在其他对象上同步 synchronized块必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法正在被调用的当前对象:synchronized(this),这正是PairManager2所使用的方式。在这种方式中,如果获得了synchronized块上的锁,那么该对象其他的synchronized方法和临界区就不能被调用量。因此,如果在this上同步,临界区的效果就会直接缩小在同步的范围内。 有时必须在另一个对象上同步,但是如果你要这么做,就必须确保所有相关的任务都是在同一个对象上同步。
class DualSynch {
private Object syncObject = new Object ( ) ;
public synchronized void f ( ) {
for ( int i = 0 ; i < 5 ; i++ ) {
System . out. println ( "f()" ) ;
Thread . yield ( ) ;
}
}
public void g ( ) {
synchronized ( syncObject) {
for ( int i = 0 ; i < 5 ; i++ ) {
System . out. println ( "g()" ) ;
Thread . yield ( ) ;
}
}
}
}
public class SyncObject {
public static void main ( String [ ] args) {
final DualSynch dualSynch = new DualSynch ( ) ;
new Thread ( ) {
public void run ( ) {
dualSynch. f ( ) ;
}
} . start ( ) ;
dualSynch. g ( ) ;
}
}
DaylSync.f()(通过同步整个方法)在this同步,而g()有一个在syncObject上同步的synchronized块。因此,这两个同步是互相独立的。通过在main中创建调用f的Thread对这一点进行演示,因为main线程是被用来调用g的。从输出中可以看到,这两个方式在同时运行,因此任何一个方法都没有因为对另一个方法的同步而阻塞。 线程本地存储 防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同对的存储。因此,如果你有5个线程都要使用变量x所表示的对象,那线程本地存储就会生成5个用于x的不同的存储块。主要是,它们使得你可以将状态与线程关联起来。 创建和管理线程本地存储可以由java.long.ThreadLocal类来实现。
import java. util. Random ;
import java. util. concurrent. ExecutorService ;
import java. util. concurrent. Executors ;
import java. util. concurrent. TimeUnit ;
class Accessor implements Runnable {
private final int id;
public Accessor ( int idn) {
id = idn;
}
@Override
public void run ( ) {
while ( ! Thread . currentThread ( ) . isInterrupted ( ) ) {
ThreadLocalVariableHolder . increment ( ) ;
System . out. println ( this ) ;
Thread . yield ( ) ;
}
}
@Override
public String toString ( ) {
return "#" + id + ": " + ThreadLocalVariableHolder . get ( ) ;
}
}
public class ThreadLocalVariableHolder {
private static ThreadLocal < Integer > value = new ThreadLocal < Integer > ( ) {
private Random random = new Random ( ) ;
protected synchronized Integer initialValue ( ) {
return random. nextInt ( 10000 ) ;
}
} ;
public static void increment ( ) {
value. set ( value. get ( ) + 1 ) ;
}
public static int get ( ) {
return value. get ( ) ;
}
public static void main ( String [ ] args) throws InterruptedException {
ExecutorService executorService = Executors . newCachedThreadPool ( ) ;
for ( int i = 0 ; i < 5 ; i++ ) {
executorService. execute ( new Accessor ( i) ) ;
}
TimeUnit . SECONDS . sleep ( 3 ) ;
executorService. shutdownNow ( ) ;
}
}
终结任务 在前面的某些示例中,cancel和isCanceled方法被放到了一个所有任务都可以看到的类中。这些任务通过检查isCanceled来确定何时终止它们自己,对于这个问题来说,这是一种合理的方式。但是,在某些情况下,任务必须更加突然地终止。 装饰性花园 在这个仿真程序中,花园委员会希望了解每天通过多个大门进入公园的总人数。每个大门都有一个十字转门或某种其他形式的计数器,并且任何一个十字转门的计数值递增时,就表示公园中的总人数的共享计数值也会递增。
import java. util. ArrayList ;
import java. util. List ;
import java. util. Random ;
import java. util. concurrent. ExecutorService ;
import java. util. concurrent. Executors ;
import java. util. concurrent. TimeUnit ;
class Count {
private int count = 0 ;
private Random rand = new Random ( 47 ) ;
public synchronized int increment ( ) {
int temp = count;
if ( rand. nextBoolean ( ) ) {
Thread . yield ( ) ;
}
return ( count = ++ temp) ;
}
public synchronized int value ( ) {
return count;
}
}
class Entrance implements Runnable {
private static Count count = new Count ( ) ;
private static List < Entrance > entries = new ArrayList < Entrance > ( ) ;
private int number = 0 ;
private final int id;
private static volatile boolean canceled = false ;
public Entrance ( int id) {
this . id = id;
entries. add ( this ) ;
}
public static void cancel ( ) {
canceled = true ;
}
@Override
public void run ( ) {
while ( ! canceled) {
synchronized ( this ) {
++ number;
}
System . out. println ( this + " Total: " + count. increment ( ) ) ;
try {
TimeUnit . MILLISECONDS . sleep ( 100 ) ;
} catch ( InterruptedException e) {
System . out. println ( "sleep interrupted" ) ;
}
}
System . out. println ( "Stopping " + this ) ;
}
public synchronized int getValue ( ) {
return number;
}
public String toString ( ) {
return "Entrance " + id + ": " + getValue ( ) ;
}
public static int getTotalCount ( ) {
return count. value ( ) ;
}
public static int sumEntrances ( ) {
int sum = 0 ;
for ( Entrance entry : entries) {
sum += entry. getValue ( ) ;
}
return sum;
}
}
public class OrnamentalGarden {
public static void main ( String [ ] args) throws InterruptedException {
ExecutorService executorService = Executors . newCachedThreadPool ( ) ;
for ( int i = 0 ; i < 5 ; i++ ) {
executorService. execute ( new Entrance ( i) ) ;
}
TimeUnit . SECONDS . sleep ( 3 ) ;
Entrance . cancel ( ) ;
executorService. shutdown ( ) ;
if ( ! executorService. awaitTermination ( 250 , TimeUnit . MILLISECONDS ) ) {
System . out. println ( "Some tasks were not terminated!" ) ;
}
System . out. println ( "Total: " + Entrance . getTotalCount ( ) ) ;
System . out. println ( "Sum of Entances: " + Entrance . sumEntrances ( ) ) ;
}
}
在阻塞时终结 前面示例中的Entrance.run在其循环中包含对sleep的调用。我们知道,sleep最终将唤醒,而任务也将返回循环的开始部分,去检查canceled标志,从而决定是否跳出循环。但是sleep一种情况,它使任务从执行状态变为阻塞状态,而有时你必须终止被阻塞的任务。 线程状态: 1:新建:当线程被创建时,它只会短暂的处于这种状态。此时它已经分配了必须的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将这个线程转变为可运行状态或阻塞状态。 2:就绪:在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。也就是说,在任意时刻,线程可以运行也可以不运行。只要调度器能分配时间片给线程,它就可以运行;这不同于死亡和阻塞状态。 3:阻塞:线程能够运行,但是有某个条件阻止它的运行。当线程处于阻塞状态时,调度器将忽略线程,不会分配给线程任何CPU时间。直到线程重新进入了就绪状态,它才有可能执行操作。 4:死亡:处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到CPU时间,它的任务已结束,或不再是可运行的。任务死亡的通常方式是从run方法返回,但是任务的线程还可以被中断,你将要看到这一点。 进入阻塞状态: 一个任务进入阻塞状态,可能有如下原因: 1:通过调用sleep使任务进入休眠状态,在这种情况下,任务在指定的时间内不会运行。 2:你通过调用wait使线程挂起。直到线程得到了notify或notifyAll消息或者signal或signalAll消息,线程才会进入就绪状态,我们将在稍后的小姐中验证这一点。 3:任务在等待某个输入输出完成。 4:任务视图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务以及获取了这个锁。 在较早的代码中,也可能会看到用suspend和resume来阻塞和唤醒线程,但是在现代java中这些方法被废止了(可能导致死锁)。stop方法也已经被废止了,因为它不释放线程获得的锁,并且如果线程处于不一致的状态,其他任务可以在这种状态下浏览并修改他们。 有时你希望能够终止处于阻塞状态的任务。如果对于处于阻塞状态的任务,你不能等待其到达代码中可以检查器状态值的某一点,因而决定让它主动地终止,那么你就必须强制这个任务跳出阻塞状态。 中断: 在Runnable.run方法的中间打断它,与等待该方法到达对cancel标志的测试等。当你打断被阻塞的任务时,可能需要清理资源。正因为这一点,在任务的run方法中间打断,更像是抛出的异常,因此在java线程中的这种类型的异常中断中用到了异常。为了在以这种方式终止任务时,返回众所周知的良好状态,你必须执行考虑代码的执行路径,并仔细编写catch子句以正确清除所有事物。 Thread类包含interrupt方法,因此你可以终止被阻塞的任务,这种方法将设置线程的中断状态。如果一个线程已经被阻塞,或者试图执行一个阻塞操作,那么设置这个线程的中断状态将抛出InterruptedException。当抛出该异常或者该任务调用Thread.interrupted时,中断将被复位。正如你将看到的,Thread.interrupted提供了离开run循环而不抛出异常的第二种方式。 为了调用interrupt,你必须持有Thread对象,你可能已经注意到了,新的concurrent类似乎在避免对Thread对象的直接操作,转而尽量通过Executor来执行所有操作,如果你在Executor上调用shutdowmNow,那么它将发送一个Interrupt调用给它启动的所有线程。这么做是有意义的,因为当你完成工程中的某个部分或者整个程序时,通常会希望同时关闭某个特定Executor的所有任务。然后,你有时也会希望只中断某个单一任务,如果使用Executor,那么通过调用submit而不是executor来启动任务,就可以持有该任务的上下文,submit将返回一个Future<?>,其中有一个未修饰的参数,因为你永远都不会在骑上调用get----持有这个Future的关键在于你可以在其上调用cancel,并因此可以使用它来中断某个特定任务。如果你将true传递给cancel,那么它就会拥有在该线程上调用interrupt以停止这个线程的权限。因此,cancel是一种中断由Executor启动的单个线程的方式。