Learning Java
(this note is based on Learning Java 3rd ed, java 5/7 was not out yet at the time. 4ed was released on july 2013)
- Chapter 1, A Modern Language
- Chapter 2, A First Application
- Chapter 3, Tools of the Trade
- Chapter 4, The Java Language
- Chapter 5, Objects in Java
- Chapter 6, Relationships Among Classes
- Chapter 7, Working with Objects and Classes
- Chapter 8, Generics
- Chapter 9: Threads
Chapter 1, A Modern Language
Chapter 2, A First Application
constructor doesn't have return type
public Hello() {
}
Event
import java.awt.event; // import java.awt.* will not import event
static member can be accessed even if no instances of the class exist.
Thread
call Thread's start()
to begin execution. once Thread starts, it continues to run until it completes its work, we interrupt it, or we stop the application.
Thread always expects to execute a method called run()
to perform the action of the thread.
Runnable Inerface force you to implement the run()
method.
Synchronization
the synchronized modifier tells Java to acquire a lock for the object that contains the method before executing that method. Only one method in the object can have the lock at any given time, which means that only one synchronized method in that object can be running at a time. When the method is done, it releases the lock on the class.
Chapter 3, Tools of the Trade
The main()
method must have the right method signature. it must be public, static method that takes an array of String objects are argument and does not return any value (void)
System Properties
system property is passed to the inerpreter on the command line using -D
option followed by name=value.
static System.getProperty()
method to access.
Classpath
the CLASSPATH
environment variable.
or pass -classpath /path/
to javac
javap
useful tool to print description of a compiled class.
you don't even need to know where the class is if it's in your classpath.
% javap java.util.stack
The Java Compiler
javac allows one public class per file and insists the file must have the same name as the class. A single file can contain multiple classes, as long as only one of the classes is public.
use -d
to specify where class files to store.
jar
% jar -cvf jarFile path // create
% jar -tvf jarFile path // list
% jar -xvf jarFile path // extract
The Default Security Manager
% java -Djava.security.manager EvilClass
policytool
% policytool
run with policy file
% java -Djava.security.manager -Djava.security.policy=EvilClass.policy EvilClass
Chapter 4, The Java Language
Javadoc Comments
block comment beginning with /**
indicates a special doc comment.
@see
@author
@version
@param
@return
@exception
@deprecated
@since
Primitive Types
- boolean
- char
- byte
- short
- int
- long
- float
- double
Reference Types
Primitive types are passed by value Reference types are always passed by reference
except arrays and interfaces.
- Arrays are special kind of object autometically created to hold a collection of some other type of object, known as base type.
- any object implements all methods of the interface can be treated as an object of that type.
from Java 5.0, Generic types and parameterized types are added.
Statements and Expressions
- statements appear inside methods and classes; they describe all activities of a Java program. Variable declarations and assignments, basic language structures such as if/then and loops are statements.
- expressions produce values; and expression is evaluated to produce a result. Method calls, object allocations and mathematical expressions are example of expressions.
- variable assignments can be used as value for futher assigments or operations, they can be considered to be both statements and expressions.
Statements
Variable declarations are limited in scope to their enclosing code block
{
int i = 5;
}
i = 6; // compile-time error, no such variable i
The null value
The expression null can be assigned to any reference type. A null reference can't be used to reference anything and attempting to do so generates a NullPointerException
at runtime.
value null is not considered an instance of any object.
String s = null;
if (s instanceof String)
// false
Stack Traces
try {
// complext, deeply nested task
} catch (Exception e) {
e.printStackTrace(System.err);
}
Checked and Unchecked Exceptions
void readFile(String s) throws IOException, InterruptedException {
...
}
the throws
clause tells the compiler that a method is a possible source of that type of checked exception and that anyone calling that method must be prepared to deal with it.
In contrast, exceptions that are subclasses of either java.lang.RuntimeException
or java.lang.Error
are unchecked. unchecked exceptions behave the same as other exceptions. We are free to catch them if we wish.
checked exceptions are intended to cover application-level problems, unchecked exceptions are intended for system-level problems.
Performance Issues of Exceptions
guarding against an exception being thrown (using a try) is free. It doesn't add any overhead to the execution of your code. However, throwing an exception is not free. When an exception is thrown, Java has to locate the appropriate try/catch block and perform other time-consuming activities at runtime.
do some test code instead of throwing exceptions frequently.
Assertions
assert false;
assert (array.length > min);
assert a > 0 : a
assert foo != null : "foo is null!"
Enable and disable assertions
% java -ea MyApplication
% java -ea:com.example.MyClass ... MyApplication
% java -ea:com.exmaple.MyClass -da:com.example.MyClass2 MyApplication
Arrays
int [] arrayOfInts; // preferred
int arrayOfInts []; // C-style
array creation
double [] someNumbers = new double [20];
Component [] widgets = new Component [12];
int [] primes = { 1, 2, 3, 4 };
String [] verbs = { "run", "jump", someWord.toString() };
the following is equivalent:
Button [] threeButtons = new Button [3];
Button [] threeButtons = { null, null, null };
Using Arrays
int num = someArray.length
for (int i : keyPad)
System.out.println(i);
array throws ArrayIndexOutOfBoundsException
Anonymous Arrays
setPets ( new Animal [] { pokey, boojum, simon } );
Multidimensional Arrays
ChessPiece [][] chessBoard;
chessBoard = new CheckPiece [8][8];
chessBoard[0][0] = new ChessPiece.Rook;
chessBoard[1][0] = new ChessPiece.Pawn;
...
Chapter 5, Objects in Java
Classes
Variables and method declarations can appear in any order, but variable initializers can't make forward references to other variables that appear later.
instance variables and static variables, every object instance has its own instance variables; static variables (class variables) are shared among all instance of an object.
static float gravAccel = 9.80;
MyClass.gravAccel = 8.76; // change for any current or future instances
static final float EARTH_G = 9.80;
constants versus enumerations
static int SIMPLE = 0, ONE_SPRING = 1, TWO_SPRING = 2;
public enum PredulumTypes { Simple, OneSpring, TwoSpring };
Methods
Now Java has variable-length argument lists for methods. (prior to Java 5.0, number and type of method arguments are fixed).
use this
to access instance variables.
this.xPos = xPos; // instance var = local var
local variables must be initialized before they can be used.
Object Creation
A constructor is a special method with the same name as its class and no return type. Constructors can be overloaded but are not inherited like other methods.
constructor overloading
A consructor can refer to another constructor in the same class or the immediate superclass using special forms of the this
and super
references.
If a constructor calls another constructor, it must do so as its first statement.
class Car {
String model;
int doors;
Car (String model, int doors) {
this.model = model;
this.doors = doors;
...
}
Car (String model) {
this(model, 4); // must be the first statement
}
}
initializer blocks
A block of code directly within the scope of a class, it's executed once at the time of the object is constructed.
Use static initializer block to set up static class members.
class ColorWheel {
static Hashtable colors = new Hashtable();
static {
colors.put("Red", Color.red);
colors.put("Green", Color.green);
...
}
}
Object Destruction
Java uses garbage collection to remove objects that are no longer needed. You can prompt the garbage collector to make a clean sweep explicitly by invoking the System.gc()
method.
Finalization
Before an object is removed by garbage collection, it's finalize()
method is invoked to give it a last opportunity to clean up its act and free other kinds of resources it may be holding.
The finalize()
methods of superclasses are not invoked automatically. use super.finalize()
to invoke it.
Weak and Soft References
Weak References are eligible for garbage collection immediately.
A Soft Reference is similar to a weak reference, but it tells the garbage collector to be less aggressive about reclaiming its contents.
java.lang.ref
package contains the WeakReference
and SoftReference
wrappers, as well as a facility called ReferenceQueue
that allows your application to receive a list of references that have been collected.
Enumerations
An enumeration is an object type in the Java language that is limited to an explicit set of values.
enum Weekday { Sunday, Monday, ... }
setDay( Weekday.Sunday );
Customizing Enumerations
public enum Weekday
{
Sunday(8), Monday(0), Tuesday(1) ... ;
int fun;
Weekday (int fun) { this.fun = fun; }
public int getFun() { return fun; }
}
Chapter 6, Relationships Among Classes
Overriding Methods
Any difference in the number or type or arguments produces two overloaded methods instead of a single, overridden method.
@Override
annotation tells the compiler that the method it marks is intended to override a method in the superclass.
overridden methods and dynamic binding
Overloaded methods are selected by the compiler at compile time. Overridden methods, on the other hand, are selected dynamically at runtime.
Static method binding
Static method can be shadowed by another static method in sub-class, as long as the original method was not declared final. But you can't have a static method and instance method with the same signature in the same class hierarchy.
final methods and performance
A profiling runtime can determine which methods are not being overridden, treating them as if they were final until it becomes necessary to do otherwise. The final keyword should not for performance considerations.
compiler optimizations
The Java compiler is smart enough to remove codes that won't be called.
static final boolean DEBUG = false;
...
final void debug(String message) {
if (DEBUG) {
System.err.println(message);
...
}
}
In this case, body of debug()
method will be optimized away, the method call would be removed entirely.
Exceptions and overridden methods
New method can redefine the throws clause, this technique is called covariant typing of the throws clause.
Return types and overridden methods
As of Java 5.0, when you override a method, you may change the return type.
Casting
You never change the object pointed to by a reference by casting it; you change only the compiler's (or runtime system's) notion of it.
use Generics to type objects.
Abstract Methods and Classes
you can't directly use a class that contains an abstract method; you must instead create a subclass that implements the abstract method's body.
abstract void vaporMethod(String name);
In Java, a class that contains one or more abstract methods must be explicitly declared as an abstract class
abstract class vaporClass {
...
abstract void vaporMethod(String name);
...
}
Interfaces
interface Driveable {
boolean startEngine();
void stopEngine();
}
Interface as Callbacks
The callback occurs when the called object subsequently invokes one of the methods. In C or C++, this is prime territory for function pointers; Java uses interfaces instead.
Interface Variables
An interface can contain constants (static final variables).
interface Scaleable {
static final int BIG = 0, MEDIUM = 1, SMALL = 2;
void setScale(int size);
}
In Java 5.0, it is better to use an enumeration or to put your constants in their own class and then use the new static import syntax to remove the hassle of referring to them.
enum SizeConstants { BIG, MEDIUM, SMALL }
// usage
static import mypackage.SizeConstants;
...
setSize(MEDIUM);
Flag interfaces
Sometimes completely empty interfaces serve as a marker that a class has a special property. The java.io.Serializeable
interface is a good example.
Subinterfaces
An interface can extend another interface.
interface DynamicallyScaleable extends Scaleable {
void changeScale(int size);
}
extends more
interface DynamicallyScaleable extends Scaleable, SomethingElseable {
...
}
Packages and Compilation Units
For most of us, a compilation unit is just a file with a .java
extension, but theoretically in an integrated development environment, it could be an arbitrary entry.
The package
statement must appear as the first statement in a compilation unit. There can be only one package statement, and it applies to the entire file.
Package Names
Package namespace is flat, not hierarchical.
Importing Classes
Other than the potential for naming conflicts, there's no penalty for importing many classes. Java doesn't carry extra baggage into the compiled class files.
Static Imports
import static java.lang.Math.*;
// usage
double circumference = 2 * PI * radius;
Visibility of Variables and Methods
- private : none
- None (default) : classes in the package
- protected : classes in package and subclasses inside or outside the package
- public : all classes
Interfaces and Visibility
Interfaces behave like classes within packages. And interface can be declared public
to make it visible outside its package. Under the default visibility, an interface is visibile only inside its package.
Like classes, only one public interface can be declared in a compilation unit.
Inner Classes
In Java, anonymous inner classes take the place of closures in other language, giving the effect of handling state and behavior independently classes.
Inner classes are pure syntactic sugar; they are not supported by the VM but are instead mapped to regular Java classes by the compiler.
Inner classes are essentially nested classes.
Class Animal {
Class Brain {
...
}
void performBehavior() { ... }
}
The Brain class "sees" all of the methods and variables of the Animal class directly in its scope.
From within Brain we can invoke the method performBehavior().
An Brain object always lives within a single instance of Animal. We'll call the object that contains any instance of Brain its enclosing instance
A Brain object cannot live outside of an enclosing instance of an Animal object. Brain always requires an enclosing instance of Animal to "hold" it.
Animal monkey = new Animal();
Animal.Brain monkeyBrain = monkey.new Brain();
Inner Classes as Adapters
A particularly important use of inner classes is to make adaptor classes. An adaptor class is a "helper" class that ties one class to another in a very specific way.
For example we want a iterator to loop employees, instead of extends and implements a new class for iterator, use inner class and an access method.
public class EmployeeList {
private Employee [] employees = ... ;
...
class Iterator implements java.util.Iterator {
...
}
Iterator getIterator() {
return new Iterator();
}
}
Inner classes can have constructors, variables and initializers. They are real classes.
Inner Classes within Methods
class Animal {
void performBehavior() {
class Brain {
...
}
}
}
The body of Brain can see anything in the scope of performBehavior()
limitation on inner classes in methods
Method invocations have limited lifetimes, but instance of inner class lives on as long as it is referenced. This means that any of the method's local variables or arguments that are referenced by the inner class must be declared final
.
void performBehavior ( final boolean nocturnal ) {
class Brain() {
void sleep() {
if ( nocturnal ) { ... }
}
}
}
Static inner classes
class Animal {
static class MigrationPattern {
...
}
...
}
// usage
Animal.MigrationPattern stl = new Animal.MigrationPattern();
Animal acts like a mini package, use import to use void full qualified name
import Animal.MigrationPattern;
Anonymous inner classes
Anonymous inner classes are an extension of the syntax of the new
operation.
Iterator getIterator() {
return new Iterator() {
// body of anonymous inner class
}; // <-- it's a statement
}
Inner classes are best used when you want to implement a few lines of code, but the verbiage and conspicuousness of declaring a separate class detracts from the task at hand.
new Thread() {
public void run() { performBehavior(); }
}.start();
Anonymous adapter classes are a perfect fit for event handling.
addMouseListener ( new MouseInputAdapter() {
public void mouseClicked(MouseEvent e) { handleClicks(e); }
} );
scope of "this" reference
Inner class has multiple this
reference, you can specify which this
you want by prefixing it with the name of the class.
class Brain {
int animalSize = Animal.this.size;
Animal ourAnimal = Animal.this;
...
}
How do inner classes really work?
class Animal {
class Brain {
}
}
comipler generates Animal.class
and Animal$Brain.class
The dollar sign is a valid character in class names but is intended for use only by automated tools.
to check
% javap 'Animal$Brain' # or Animal.Brain with Java 5.0
to pack
% jar cvf animal.jar Animal*class
Chapter 7, Working with Objects and Classes
The Object Class
Every object has a toString()
method. PrintStream objects use toString()
to print data.
MyObj myObject = new MyObj();
System.out.println( myObject );
Equality and Equivalence
equals()
determines whether two objects are equivalent.
String userName = "Joe";
...
if (userName.equals( suspectName ))
arrest ( userName );
Using equals()
is not the same as:
if (userName == suspectName) // wrong
This statement tests whether the two reference variables, userName and suspectName, refer to the same object. It is a test for identity, not equality.
In order to override (not overload) equals()
, the method must specify its arguments to be an Object
class Sneakers extends Shoes {
public boolean equals ( Object arg ) {
...
}
}
Hashcodes
The hashCode()
method returns an integer that is a hashcode for the object. A hashcode is like an signature or checksum for an object. The hashcode should always be different for instances of the class that contain different data, but should be the same for instances that compares "equal" with the equals()
method.
The default implementation of hashCode()
assigns each object instance a unique number.
Cloning Objects
To make itself cloneable, an object must implement the java.lang.Cloneable
interface.
clone()
is a protected method. If we want to an object cloneable by everyone, override its clone()
method and make it public.
import java.util.HashMap;
public class Sheep implements Cloneable {
HashMap flock = new HashMap();
public Object clone() {
try {
return super.clone();
} catch ( CloneNotSupportedException e ) {
throw new Error("This should never happen!");
}
}
}
// usage
Sheep one = new Sheep();
Sheep anotherOne = (Sheep)one.clone();
The cast is neccessary here because the return type of clone()
is Object. In Java 5.0, we could do
public Sheep clone() {
...
return (Sheep)super.clone();
}
Sheep anotherOne = one.clone();
The Class Class
Classes in Java source code are represented at runtime by instances of the java.lang.Class
class. There's a Class object for every object type you use; this Class object is responsible for producing instances of that type.
We get the Class associated with a particular object with the getClass()
method
String myString = "foo";
Class StringClass = myString.getClass();
We can also get the class reference using the .class
notation
Class StringClass = String.class;
The .class
reference looks like a static field that exists in every class. However, it is really resolved by the compiler;
String s = "Boofa!";
Class stringClass = s.getClass();
system.out.println( stringClass.getName() ); // get name
try {
String s2 = (String)stringClass.newInstance();
}
catch (InstantiationException e) { .. }
catch (IllegalAccessException e) { .. }
newInstance()
return an Object, so we have to cast it. But the Class class is a generic class, we can parameterize it to be more specific Java type.
Class<String> stringClass = String.class;
try {
String s2 = stringClass.newInstance(); // no cast necessary
}
catch ...
forName()
is a static method of Class that returns a Class object given its name as a String
try {
Class sneakersClass = Class.forName("Sneakers");
} catch ( ClassNotFoundException e ) { .. }
Reflection
Java Reflection API is supported by the classes in the java.lang.reflect
package.
The three primary features of a class are its fields (variables), methods and constructors. They are represented by java.lang.reflect.Field
, java.lang.reflect.Method
and java.lang.reflect.Constructor
API.
(a lots of details, read the book again)
Annotations
Annotations allow you to add metadata to Java classes, methods, and fields.
The apt Tool
The Java 5.0 SDK ships a command-line Annotation Processing Tool, apt, that is a sort of frontend to the javac compiler. apt uses pluggable annotation proccessors to process the annotations in source files before the code is compiled by javac.
Chapter 8, Generics
Generics are also referred to as parameterized types. In other languages, generics are sometimes referred to as templates.
A generic class requires one or more type parameters wherever we refer to the class type and uses them to customize itself.
List<String> listOfStrings;
List<Date> dates;
list<java.math.BigDecimal> decimals;
Completing the type by supplying its type parameter is called instanitiating the type. It is also sometimes called invoking the type.'
Erasure
The compiler has erased all of the angle bracket syntax and replaced the type variable in our List class with a type that can work at runtime with any allowed type: (in book's case), Object.
We can't hava a class that implements two different generic List instantiations because they are really the same type at runtime and there is no way to tell them apart.
Raw Types
Every generic has a raw type. The class of List<Date>
and List<String>
share the plain old Java class List. List is called the raw type of the generic class.
Java 5.0 compiler will warn you if raw type is used
// non-generic code using the raw type
List list = new ArrayList();
list.add("foo"); // warning
use -Xlint:unchecked
to get more details
% javac -Xlint:unchecked MyClass.java
We can place limitations or bounds on the parameter types.
class Bounded< E extends Date > {
public void addElement( E element ) { ... }
}
Date is called the upper bound of the type.
Why isn't a List<Date> a List<Object>?
If we could assign DateList to an ObjectList variable, we would have to be able to use Object methods to insert elements of types other than Date into it. And Java generics have no runtime representation.
Writing Generic Classes
One or more type variables are declared in the angle bracket (<>
) type declaration and used throughout the body and instance methods of the class.
class Trap<T> {
T trapped;
public void snare(T trapped) { this.trapped = trapped; }
public T release() { return trapped; }
}
Subclassing Generic
A nongeneric subclass must extend a particular instantiation of the parent type, filling in the required parameters to make it concrete:
class DateList extends ArrayList<Date> { ... }
A generic subtype of a generic class may extend either a concrete instanitation of the class or it may share a type variable that is "passes up" to the parent upon instaniation.
class AdjustableTrap<T> extends Trap<T> { ... }
Exceptions and Generics
Type variables may be used to define the type of exceptions thrown by methods, but to do so we need to introduce the concept of bounds.
<T extends Throwable>
No generic Throwables
A type variable can be used to specify the type of Throwable in the throws clause of a method. However, we can not use generics to create new types of exceptions. No Generic subtypes of Throwable are allowed.
Generic Throwable would require try/catch blocks that can differentiate instantiations of Throwable. Since there is no runtime representation of generics, this isn't possible with erasure.
Bounds
Bounds use the extends keyword and some new syntax to limit the parameter types that may be applied to a generic type.
class EmployeeList<T extends Employee> { ... }
We could further require that the Employee type implement one or more interfaces using the special & syntax
class EmployeeList<T extends Employee & Ranked & Printable> { ... }
The order of the & interface bounds is not significant, but only one class type can be specified and if there is one, it must come first.
Ensure and Bounds
The type after erasure used for the parameter type of a generic class is the leftmost bound.
Insert a somewhat gratuitous additional type Object as the leftmost bound in order to get back our old API.
class List<E extends Object & Listable> { ... }
Inserting Object doesn't change the actual bounds of the generic class but does change the erased signature.
Wildcards
The ?
wildcard by itself is called the unbounded wildcard and denotes that any type instantiation is acceptable.
List<?> anyInstantiationOfList = new ArrayList<Date>();
Bounded Wildcards
A bounded wildcard uses the extends keyword to limit the range of assignable types.
List<? extends Date> dateInstantiations = new ArrayList<Date>();
May extend interfaces as well
Trap<? extends Catchable & Releaseable> trap;
Lower bounds
Wildcard instantiations actually allow another type of bound called a lower bound as well. A lower bound is specified with the keyword super and requires that intantiations be of a certain type or any of its supertypes, up to Object.
List< ? super MyDate > listOfAssignableFromMyDate;
listOfAssignableFromMyDate = new ArrayList<MyDate>();
listOfAssignableFromMyDate = new ArrayList<Date>();
listOfAssignableFromMyDate = new ArrayList<Object>();
Lower bounds are useful for cases where we want to be sure that a particular container instantiation can hold a particular element type, without limiting it to just the specific type of the element.
Only the wildcard instantiation syntax can use the super keyword to refer to lower bounds.
(gave up, read later)
Chapter 9: Threads
The Thread Class and Runnable Interface
All execution in Java is associated with a Thread object, beginning with a "main" thread that is started by the Java VM to launch your application. A new thread is born when we create an instance of the java.lang.Thread
class.
Every thread begins its life by executing the run()
method in a Runnable
object.
public interface Runnable {
abstract public void run();
}
A new born thread remains idle until we call its start()
method. The thread then wakes up and proceeds to execute the run()
method of its target object. start()
can be called only once in the lifetime of a thread.
class Animation implements Runnable {
boolean animate = true;
public void run() {
while (animate) {
...
}
}
Thread my thread;
Animation (String name) {
myThread = new Thread(this);
myThread.start();
}
...
}
or
class Animation extends Thread {
boolean animate = true;
public void run() {
while (animate) {
...
}
}
}
better, using an adapter
class Animation {
public void startAnimating() {
...
Thread myThread = new Thread ( new Runnable() {
public void run() { drawFrames(); }
});
myThread.start();
}
private void drawFrames() {
...
}
}
without saving a reference
new Thread() {
public void run() { drawFrames(); }
}.start();
Controlling Threads
- The static
Thread.sleep()
causes the currently executing thread to wait for a designated period of time, without consumming much (or possibly any) CPU time. wait()
andjoin()
coordinate the execution of two or more threads.interrupt()
wakes up a thread that is sleeping in asleep()
orwait()
operation or is otherwise blocked on a long I/O operation.
stop()
, suspend()
and resume()
are deprecated.
The sleep() method
tell a thread to sit idle or sleep for a fixed period of time.
try {
Thread.sleep(1000); // milliseconds, current thread
} catch ( InterruptedException e) { ... }
The join() method
If you need to coordinate your activities with another thread by waiting for it to complete its task, use join()
. Calling join()
causes the caller to block until target thread completes.
This is a very coarse form of thread synchronization. A more general and powerful mechanism is using wait()
and nofity()
and higher-level API in java.util.concurrent package.
The interrupt() method
When a thread is interrupted, its interrupt status flag is set, whether the thread is idle or not. The thread can test this status with isInterrupted()
. Another form isInterrupted(boolean)
accepts a Boolean value indicating whether or not to clear the interrupt status.
Death of a Thread
A thread continues to execute until one of following happens:
- It explicitly reurns from its target
run()
method. - It encounters an uncaught runtime exception.
- The evil and nasty deprecated
stop()
method is called.
The setDaemon()
method can be used to mark a thread as a daemon thread that should be killed and discarded when no other nondaemon application thread remain.
class Devil extends Thread {
Devil() {
setDaemon(true);
start();
}
public void run() { ... }
}
Synchronization
In Java, every object has an associated lock. To be more specific, every class and every instance of a class has its own lock. The synchronized
keyword marks places where a thread must acquire the lock before proceeding.
class SpeechSynthesizer {
synchronized void say (String word) { ... }
}
In addition to synchronizing entire methods, the synchronized
keyword can be used in a special construct to guard arbitrary blocks of code.
synchronized ( myObject ) {
...
}
below is equivalent
synchronized void myMethod() { ... }
void myMethod() {
synchronized (this ) { ... }
}
Accessing variables
Do individual variable types need to be synchronized? Normally, the answer is no. Almost all operations on primitives and object reference types in Java happen atomically: they are handled by the VM in one step, with noo opportunity for two threads to collide.
But double and long types are not guaranteed to be handled atomically. Both of them represent 64-bit values. The problem has to do with how the Java VM's stack handles them. For now, to be strict, you should synchronize access to double and long instance variables through accessor methods or use the volatile
keyword or an atomic wrapper class.
(something about memory barrier). The volatile
keyword indicates to the VM that the value may be changed by extenal threads and effectively synchronizes access to it automatically.
Java 5.0 added java.util.concurrent.atomic
that provides synchronized wrapper classes for all primitive types and references.
Reentrant locking
The locks acquired by Java upon entering a synchronized method or block of code are reentrant, meaning that the thread holding onto the lock may acquire the same lock again any number of times and never blocks waiting for itself.
The wait() and notify() methods
Every object in Java is a subclass of Object, so every object inherits wait()
and notify()
methods.
By calling wait()
, the thread release lock and goes to sleep until the other thread notify()
on the target object. The first thread won't wake up from the wait()
unless another thread calls notify()
.
An overloaded version of wait()
allows us to specify a timeout period. If another thread doesn't call notify()
in the specified period, the waiting thread automatically wakes up.
class MyThing {
synchronized void waiterMethod() {
// do some stuff
wait(); // wait for notifier
// continue doing stuff
}
synchronized void notifierMethod() {
// do some stuff
notify(); // notify waiter
// continue doing stuff
}
synchronized void relatedMethod() {
// do some related stuff
}
}
- waiter thread comes first, hold the lock. notifier and related threads are waiting for the lock.
- waiter executed
wait()
(released lock and went to sleep) - (assume notifier is the next) notifier acquires lock.
- notifier executes
notify()
(waiter wakes up and rejoins related in vying for the lock) - We'll let you choose your own ending for the story.. (WTF??)
For each notify()
call, the runtime system wakes up only one thread that is asleep in a wait()
call. If multiple threads are waiting, Java picks a thread on an arbitrary basis. The Object class also provides a notifyAll()
call to wake up all waiting threads.
wait conditions
we want our waiter thread to sit in a loop
while (condition != true)
wait();
This test is called the wait condition. It's important to use a loop on the wait condition to be sure that the thread has been awakened for the right reason.
(the rest of the chapter is also a good read, including modern ways to create threads, but no notes were taken)
(afer this chapter, all core java language: syntax and build-in features have been covered. rest of the book is about API and libraries)