Chapter 14 Threads

1) You can create a thread like the following statement:

Thread worker = new Thread();

A thread is a thread of execution in a program. The Java Virtual Machine allows an application to have multiple threads of execution running concurrently.

Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority. Each thread may or may not also be marked as a daemon. When code running in some thread creates a new Thread object, the new thread has its priority initially set equal to the priority of the creating thread, and is a daemon thread if and only if the creating thread is a daemon.

When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs:

  • The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place.
  • All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method.

There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:


	class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime  . . .
         }
     }

The following code would then create a thread and start it running:

PrimeThread p = new PrimeThread(143);
p.start();

The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following:


	class PrimeRun implements Runnable {
         long minPrime;
         PrimeRun(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime  . . .
         }
     }

The following code would then create a thread and start it running:

PrimeRun p = new PrimeRun(143);
new Thread(p).start();

Every thread has a name for identification purposes. More than one thread may have the same name. If a name is not specified when a thread is created, a new name is generated for it.

Unless otherwise noted, passing a null argument to a constructor or method in this class will cause a NullPointerException to be thrown.

 

Constructors:

Thread()

Allocates a new Thread object.

Thread(Runnable target)

Allocates a new Thread object.

Thread(Runnable target, String name)

Allocates a new Thread object.

Thread(String name)

Allocates a new Thread object.

Thread(ThreadGroup group, Runnable target)

Allocates a new Thread object.

Thread(ThreadGroup group, Runnable target, String name)

Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group.

Thread(ThreadGroup group, Runnable target, String name, long stackSize)

Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group, and has the specified stack size.

Thread(ThreadGroup group, String name)

Allocates a new Thread object.

 

  You can give a thread a name, either as a String parameter to the constructor or as the parameter of a setName invocation. You can get the current name of a thread by invoking getName.

  You can invoke start only once for each thread – invoking it again results in an IllegalThreadStateException.

  You can obtain the Thread object for the currently running thread by invoking the static method Thread.currentThread.

 

  The work that you define within a run method is normally of a fairly private nature, but run is public and so can be invoked indiscriminately by anyone with access to your object. To solve the problem, you could define an inner Runnable object:

class Worker {
	String content;
	Worker(String content) {
		this.content = content;
		Runnable service = new Runnable(){
			public void run() {
				try{
					while(true) {
						System.out.println(Worker.this.content);
						Thread.sleep(3000);
					}
				} catch(InterruptedException e) {
					return;
				}
			}
		};
		
		new Thread(service).start();
	}
}

public class HiddenRun {
	public static void main(String[] args) {
		Worker w1 = new Worker("Ping");
		Worker w2 = new Worker("Pong");
	}
}

2) Every object has a lock associated with it, and that lock can be acquired and released through the use of synchronized methods and statements. The term synchronized code describes any code that is inside a synchronized method or statement.

  Synchronization forces execution of the two threads to be mutually exclusive in time. Locks are owned per thread, so invoking a synchronized method from within another method synchronized on the same object will proceed without blocking, releasing the lock only when the outermost synchronized method returns. This per-thread behavior prevents a thread from blocking on a lock it already has, and permits recursive method invocations and invocations of inherited methods, which themselves may be synchronized.

Example:

class Data {
	private int data;
	
	public Data(int data){this.data = data;}
	
	public synchronized int getData(){ return this.data; }
	
	public synchronized void setData(int data){ this.data = data; }
	
	public synchronized String toString(){ return String.valueOf(this.data); }
}

class MyThread implements Runnable {
	Data data;
	
	MyThread(Data data) {
		this.data = data;
	}
	
	public void run() {
		try{
			while(true) {
				System.out.println(Thread.currentThread().getName() + ": " + this.data);
				this.data.setData(this.data.getData() + 1);
				Thread.sleep(3000);
			}
		} catch(InterruptedException e) {
			return;
		}
	}
}

public class SynchronizedTest {

	public static void main(String[] args) {
		Data data = new Data(0);
		Thread t1 = new Thread(new MyThread(data), "Thread 1");
		Thread t2 = new Thread(new MyThread(data), "Thread 2");
		t1.start();
		t2.start();
	}
}

Output:

Thread 1: 0
Thread 2: 0
Thread 1: 2
Thread 2: 2
Thread 1: 4
Thread 2: 4
Thread 1: 6
Thread 2: 6
Thread 1: 8
Thread 2: 8

 

  The lock is released as soon as the synchronized method terminateswhether normally, with a return statement or by reaching the end of the method body, or abnormally by throwing an exception.

  You can ask whether the current thread holds the lock on a given object by passing that object to the Thread class's static holdsLock method, which returns true if the current thread does hold the lock on that object.

  Static methods can also be declared synchronized. Associated with every class is a Class object. A static synchronized method acquires the lock of the Class object for its class. Two threads cannot execute static synchronized methods of the same class at the same time, just as two threads cannot execute synchronized methods on the same object at the same time. If static data is shared between threads then access to it must be protected by static synchronized methods.

  Acquiring the Class object lock in a static synchronized method has no effect on any objects of that class. You can still invoke synchronized methods on an object while another thread holds the Class object lock in a static synchronized method. Only other static synchronized methods are blocked.

 

  The synchronized statement enables you to execute synchronized code that acquires the lock of any object, not just the current object, or for durations less than the entire invocation of a method. The synchronized statement has two parts: an object whose lock is to be acquired and a statement to execute when the lock is obtained. The general form of the synchronized statement is

synchronized (expr) {
    statements
}

  A synchronized method is simply a syntactic shorthand for a method whose body is wrapped in a synchronized statement with a reference to this.

  The synchronized statement has a number of uses and advantages over a synchronized method:

  (1) It can define a synchronized region of code that is smaller than a method.

  (2) synchronized statements allow you to synchronize on objects other than this. It may be that different groups of methods within a class act on different data within that class, and so while mutual exclusion is needed within a group, it is not needed between groups. Instead of making all the methods synchronized, you can define separate objects to be used as locks for each such group and have the methods use synchronized statements with the appropriate lock object.

  (3) If you need a synchronized statement to use the same lock used by static synchronized methods, you can use the class literal for your class:

Body() {
    synchronized (Body.class) {
        idNum = nextID++;
    }
}

 

3) The Java programming language use wait, notify and notifyAll to provide a way to communicate between threads.

The wait pattern:

synchronized void doWhenCondition() {
    while (!condition)
        wait();
    … Do what must be done when the condition is true …
}

Cautions:

(1) Everything is executed within synchronized code. If it were not, the state of the object would not be stable. For example, if the method were not declared synchronized, then after the while statement, there would be no guarantee that the condition remained TRue: Another thread might have changed the situation that the condition tests.

(2) One of the important aspects of the definition of wait is that when it pauses the thread, it atomically releases the lock on the object. Saying that the thread suspension and lock release are atomic means that they happen together, indivisibly. Otherwise, there would be a race hazard: A notification could happen after the lock is released but before the thread is suspended. The notification would have no effect on the thread, effectively getting lost. When a thread is restarted after being notified, the lock is atomically reacquired.

(3) The condition test should always be in a loop. Never assume that being awakened means that the condition has been satisfiedit may have changed again since being satisfied. In other words, don't change the while to an if.

The notify pattern:

synchronized void changeCondition() {
    … change some value used in a condition test …
    notifyAll(); // or notify()
}

There are three forms of wait and two forms of notification. All of them are methods in the Object class, and all are final so that their behavior cannot be changed:

public final void wait(long timeout) tHRows InterruptedException

The current thread waits until one of four things happens: notify is invoked on this object and this thread is selected to be runnable; notifyAll is invoked on this object; the specified timeout expires; or the thread has its interrupt method invoked. timeout is in milliseconds. If timeout is zero, the wait will not time out but will wait indefinitely for notification. During the wait the lock of the object is released and is automatically reacquired before wait completes regardless of how or why wait completes. An InterruptedException is thrown if the wait completes because the thread is interrupted.

public final void wait(long timeout, int nanos) tHRows InterruptedException

A finer-grained wait, with the time-out interval as the sum of the two parameters: timeout in milliseconds and nanos in nanoseconds, in the range 0999999).

public final void wait() throws InterruptedException

Equivalent to wait(0).

public final void notifyAll()

Notifies all the threads waiting for a condition to change. Threads will return from the wait invocation once they can reacquire the object's lock.

public final void notify()

Notifies at most one thread waiting for a condition to change. You cannot choose which thread will be notified, so use this form of notify only when you are sure you know which threads are waiting for what at which times. If you are not sure of any of these factors, you should use notifyAll.

  A running thread continues to run until it performs a blocking operation (such as wait, sleep, or some types of I/O) or it is preempted. A thread can be preempted by a higher-priority thread becoming runnable or because the thread scheduler decides it's another thread's turn to get some cyclesfor example, time slicing limits the amount of time a single thread can run before being preempted.

  You can change the priority using setPriority with a value between THRead's constants MIN_PRIORITY and MAX_PRIORITY. The standard priority for the default thread is NORM_PRIORITY.

  Using small "delta" values around the normal priority level is usually preferable to using MIN_PRIORITY or MAX_PRIORITY directly.

  Several methods of the Thread class allow a thread to relinquish its use of the CPU:

public static void sleep(long millis) tHRows InterruptedException

Puts the currently executing thread to sleep for at least the specified number of milliseconds. "At least" means there is no guarantee the thread will wake up in exactly the specified time. Other thread scheduling can interfere, as can the granularity and accuracy of the system clock, among other factors. If the thread is interrupted while it is sleeping, then an InterruptedException is thrown.

public static void sleep(long millis, int nanos) tHRows InterruptedException

Puts the currently executing thread to sleep for at least the specified number of milliseconds and nanoseconds. Nanoseconds are in the range 0999999.

public static void yield()

Provides a hint to the scheduler that the current thread need not run at the present time, so the scheduler may choose another thread to run. The scheduler may follow or ignore this suggestion as it sees fityou can generally rely on the scheduler to "do the right thing" even though there is no specification of exactly what that is.

  Although not mentioned in the API documents, println is very likely to be synchronized in the JVM implementation.

 

4) A thread that has been started becomes alive and the isAlive method will return TRue for that thread. A thread continues to be alive until it terminates, which can occur in one of three ways:

  • The run method returns normally.

  • The run method completes abruptly.

  • The application terminates.

 

public void interrupt()

Interrupts this thread.

Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

If this thread is blocked in an I/O operation upon an interruptible channel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a ClosedByInterruptException.

If this thread is blocked in a Selector then the thread's interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector's wakeupmethod were invoked.

If none of the previous conditions hold then this thread's interrupt status will be set.

Interrupting a thread that is not alive need not have any effect.

Throws:
SecurityException - if the current thread cannot modify this thread
 
public static boolean interrupted()

Tests whether the current thread has been interrupted. The interrupted status of the thread is cleared by this method. In other words, if this method were to be called twice in succession, the second call would return false (unless the current thread were interrupted again, after the first call had cleared its interrupted status and before the second call had examined it).

A thread interruption ignored because a thread was not alive at the time of the interrupt will be reflected by this method returning false.

Returns:
true if the current thread has been interrupted; false otherwise.
See Also:
isInterrupted()
 
public boolean isInterrupted()

Tests whether this thread has been interrupted. The interrupted status of the thread is unaffected by this method.

A thread interruption ignored because a thread was not alive at the time of the interrupt will be reflected by this method returning false.

Returns:
true if this thread has been interrupted; false otherwise.
See Also:
interrupted()

 

One thread can wait for another thread to terminate by using one of the join methods.

public final void join(long millis) tHRows InterruptedException

Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever. If the thread is interrupted while it is waiting you will get an InterruptedException.

public final void join(long millis, int nanos) tHRows InterruptedException

Waits for this thread to finish, with more precise timing. Again, a total time-out of zero nanoseconds means to wait forever. Nanoseconds are in the range 0999,999.

public final void join() throws InterruptedException

Equivalent to join(0).

Example:

class Run implements Runnable {
	int val;
	Run(){this.val = 0;}
	public void run() {
		try {
			System.out.println("Running in 5 seconds ...");
			Thread.sleep(5000);
			this.val = 100;
		} catch(InterruptedException e) {
			return;
		}
	}
}

public class JoinTest {

	public static void main(String[] args) {
		Run run = new Run();
		Thread t = new Thread(run);
		t.start();

		try {
			t.join(1000);
			System.out.println(run.val);
		} catch(InterruptedException e) {
			System.out.println("Interrupted!");
		}
	}
}

Output:

Running in 5 seconds ...
0

 

5) There are two kinds of threads: user and daemon. The presence of a user thread keeps the application running, whereas a daemon thread is expendable. When the last user thread is finished, any daemon threads are terminated and the application is finished. This termination is like that of invoking destroyabrupt and with no chance for any cleanupso daemon threads are limited in what they can do. You use the method setDaemon(true) to mark a thread as a daemon thread, and you use isDaemon to test that flag. By default, daemon status is inherited from the thread that creates the new thread and cannot be changed after a thread is started; an IllegalThreadStateException is thrown if you try.

  You can force an application to end by invoking either the System or Runtime method exit.

 

6) The rules that determine how memory accesses are ordered and when they are guaranteed to be visible are known as the memory model of the Java programming language. The memory model defines the set of allowed values that can be returned when a variable is read.

 

  Fields (but not array elements) can be declared with the volatile modifier. A write to a volatile variable synchronizes with all subsequent reads of that variable.

Example:

class Data {
	private int data;
	
	Data(int data) { this.data = data; }
	void setData(int data) { this.data = data; }
	int getData() { return this.data; }
	public String toString() { return String.valueOf(this.data); }
}

public class FinalTest {

	public static void main(String[] args) {
		final Data d = new Data(100); // a final reference, that is the d can't refer to another object
		System.out.println(d);
		d.setData(1000);
		System.out.println(d);
		
		d = new Data(10000); // Compile error: The final local variable d cannot be assigned. 
		 					 // It must be blank and not using a compound assignment
	}
}

Output:

100
1000

 

So be cautious to synthesize the final fields as well when design an multithreading program.

 

7) Threads are organized into thread groups for management and security reasons. A thread group can be contained within another thread group, providing a hierarchy originating with the top-level or system thread group. Generally speaking, security-sensitive methods always check with any installed security manager before proceeding. If the security policy in force forbids the action, the method throws a SecurityException.

  Every thread belongs to a thread group. Each thread group is represented by a ThreadGroup object that describes the limits on threads in that group and allows the threads in the group to be interacted with as a group. You can specify the thread group for a new thread by passing it to the thread constructor. By default each new thread is placed in the same thread group as that of the thread that created it, unless a security manager specifies differently.

  You can check whether you are alllowed to modify a Thread by invoking its checkAccess method, which throws SecurityException if you cannot modify the thread and simply returns if you can(it is a void method).

  A thread group can be a daemon groupwhich is totally unrelated to the concept of daemon threads. A daemon ThreadGroup is automatically destroyed when it becomes empty. Setting a ThreadGroup to be a daemon group does not affect whether any thread or group contained in that group is a daemon. It affects only what happens when the group becomes empty.

  Thread groups can also be sued to set an upper limit on the priority of the threads they contain:

public final void setMaxPriority(int pri)

Sets the maximum priority of the group. Threads in the thread group that already have a higher priority are not affected.

First, the checkAccess method of this thread group is called with no arguments; this may result in a security exception.

If the pri argument is less than Thread.MIN_PRIORITY or greater than Thread.MAX_PRIORITY, the maximum priority of the group remains unchanged.

Otherwise, the priority of this ThreadGroup object is set to the smaller of the specified pri and the maximum permitted priority of the parent of this thread group. (If this thread group is the system thread group, which has no parent, then its maximum priority is simply set to pri.) Then this method is called recursively, with pri as its argument, for every thread group that belongs to this thread group.

Parameters:
pri - the new priority of the thread group.
Throws:
SecurityException - if the current thread cannot modify this thread group.
Since:
JDK1.0
See Also:
getMaxPriority(), SecurityException, checkAccess()
 

ThreadGroup supports the following constructors and methods:

public ThreadGroup(String name)

Creates a new ThreadGroup. Its parent will be the ThreadGroup of the current thread. Like Thread names, the name of a group is not used by the runtime system but it can be null.

public ThreadGroup(ThreadGroup parent, String name)

Creates a new ThreadGroup with a specified name in the ThreadGroup parent . A NullPointerException is thrown if parent is null.

public final String getName()

Returns the name of this ThreadGroup.

public final ThreadGroup getParent()

Returns the parent ThreadGroup, or null if it has none (which can only occur for the top-level thread group).

public final void setDaemon(boolean daemon)

Sets the daemon status of this thread group.

public final boolean isDaemon()

Returns the daemon status of this thread group.

public final void setMaxPriority(int maxPri)

Sets the maximum priority of this thread group.

public final int getMaxPriority()

Gets the current maximum priority of this thread group.

public final boolean parentOf(ThreadGroup g)

Checks whether this thread group is a parent of the group g, or is the group g. This might be better thought of as "part of," since a group is part of itself.

public final void checkAccess()

Throws SecurityException if the current thread is not allowed to modify this group. Otherwise, this method simply returns.

public final void destroy()

Destroys this thread group. The thread group must contain no threads or this method throws IllegalThreadStateException. If the group contains other groups, they must also be empty of threads. This does not destroy the threads in the group.

You can examine the contents of a thread group using two parallel sets of methods: One gets the threads contained in the group, and the other gets the thread groups contained in the group.

public int activeCount()

Returns an estimate of the number of active threads in this group, including threads contained in all subgroups. This is an estimate because by the time you get the number it may be out of date. Threads may have died, or new ones may have been created, during or after the invocation of activeCount. An active thread is one for which isAlive returns true.

public int enumerate(Thread[] threadsInGroup, boolean recurse)

Fills the threadsInGroup array with a reference to every active thread in the group, up to the size of the array, and returns the number of threads stored. If recurse is false, only threads directly in the group are included; If it is TRue, all threads in the group's hierarchy will be included. ThreadGroup.enumerate gives you control over whether you recurse, but THReadGroup.activeCount does not. You can get a reasonable estimate of the size of an array needed to hold the results of a recursive enumeration, but you will overestimate the size needed for a non-recursive enumerate.

public int enumerate(Thread[] threadsInGroup)

Equivalent to enumerate(threadsInGroup,true) .

public int activeGroupCount()

Like activeCount, but counts groups, instead of threads, in all subgroups. "Active" means "existing." There is no concept of an inactive group; the term "active" is used for consistency with activeCount.

public int enumerate(ThreadGroup[] groupsInGroup, boolean recurse)

Like the similar enumerate method for threads, but fills an array of ThreadGroup references instead of THRead references.

public int enumerate(ThreadGroup[] groupsInGroup)

Equivalent to enumerate(groupsInGroup,true) .

 

8) You can invoke the following methods on a Thread object to help you debug your threads:

public String toString()

Returns a string representation of the thread, including its name, its priority, and the name of its thread group.

public long getId()

Returns a positive value that uniquely identifies this thread while it is alive.

public Thread.State getState()

Returns the current state of this thread. Thread.State is a nested enum that defines the constants: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, and TERMINATED. A newly created thread has state NEW, until it is started, at which time it becomes RUNNABLE until it terminates and becomes TERMINATED. While a thread is runnable, but before it terminates it can switch between being RUNNABLE and being BLOCKED (such as acquiring a monitor lock), WAITING (having invoked wait), or TIMED_WAITING (having invoked a timed version of wait).

public static void dumpStack()

Prints a stack trace for the current thread on System.err.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章