概述
并发问题常常以微妙和偶然的方式发生,如果视而不见,就会遭其反噬,所以并发需要深入研究。此外了解并发可以使你意识到明显正确的程序可能会展示出不正确的行为。要理解并发编程,其难度与理解面向对象编程差不多。如果你花点儿工夫,就能明白其基本机制,但要想真正地掌握它的实质,就需要深入的学习和理解。
一、并发的多面性
用并发解决的问题大体上可以分为“速度”和“设计可管理性”两种
**速度
并发通常是提高运行在单处理器上的程序的性能,因为线程会因为该线程控制范围之外的某些条件(通常是I/O)而导致不能继续执行(即为阻塞),并发就是为了解决阻塞带来的性能问题。
**改进代码设计
线程可以使我们能够创建更加松耦合的设计。
二、基本的线程机制
一个线程就是在进程中的一个单一的顺序控制流,线程模型为编程带来了便利,它简化了在单一程序中同时交织在一起的多个操作的处理。使用线程机制是一种建立透明的、可扩展的程序的方法。
1、定义任务
定义任务,需要实现Runnable接口并编写run()方法。
Thread.yield() // 是对线程调度器的建议,它在声明:“我已经执行完生命周期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机。”
例如,下例给出一个任务,并将任务交给main线程执行:
public class LiftOff implements Runnable {
protected int countDown = 10; // Default
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff() {}
public LiftOff(int countDown) {
this.countDown = countDown;
}
public String status() {
return "#" + id + "(" +(countDown > 0 ? countDown : "Liftoff!") + "). ";
}
public void run() {
while (countDown-- > 0) {
System.out.print(status());
Thread.yield();
}
System.out.print("\n");
}
}
public class MainThread {
public static void main(String[] args) {
LiftOff launch = new LiftOff();
launch.run();
}
}
2、Thread类
将任务提交给一个Thread构造器来构建一个可运行的线程。然后调用Thread对象的start()方法为该线程执行必须的初始化操作并调用Runnable的run()方法。 注:每个Thread都“注册”了它自己,因此确实有一个对它的引用,而且在它的任务退出其run()并死亡之前,垃圾回收器无法清除它。
例子:
public class MoreBasicThreads {
public static void main(String[] args) {
for (int i=0; i<5; i++) {
new Thread(new LiftOff()).start();
}
System.out.println("Waiting for LiftOff");
}
}
3、使用Executor
Java SE5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。Executor允许你管理异步任务的执行,而无须显式地管理线程的声明周期。Executor在Java SE5/6中是启动任务的优选方法。
ExecutorService(具有服务声明周期的Executor,例如关闭)知道如何构建恰当的上下文执行Runnable对象。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i=0; i<5; i++) {
exec.execute(new LiftOff());
}
exec.shutdown();
}
}
注:shutdown()方法可以防止新任务被提交给这个Executor。CachedThreadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选。只有当这种方式会引发问题时,才需要切换到FixedThreadPool。
注:SingleThreadExecutor就像是线程数量为1的FixedThreadPool。这对于长期存活的线程和事件分发线程很有用。如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前运行结束。SingleThreadExecutor会序列化所有提交给它的任务,并维护自己(隐藏)的悬挂任务队列。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutor {
public static void main(String[] args) {
ExecutorService exec = Executors.newSingleThreadExecutor();
for (int i=0; i<5; i++) {
exec.execute(new LiftOff());
}
exec.shutdown();
}
}
4、从任务中返回值
Runnable是执行工作的独立任务,但是它不返回任何值。如果希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call中返回的值,并且必须使用ExecutorService.submit()方法调用它。
例如:
import java.util.concurrent.Callable;
public class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id) {
this.id = id;
}
public String call() {
return "result of TaskWithResult " + id;
}
}
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableDemo {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<Future<String>>();
for (int i=0; i<10; i++) {
results.add(exec.submit(new TaskWithResult(i)));
}
for (Future<String> fs : results) {
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
System.out.println(e);
return;
} catch (ExecutionException e) {
System.out.println(e);
} finally {
exec.shutdown();
}
}
}
}
注:submit()方法会产生Future对象。它用Callable返回结果的特定类型进行了参数化。可以使用isDone()方法来查询Future是否已经完成。当任务完成时,它具有一个结果,你可以调用get方法来获取该结果。如果不用isDone()进行检查就直接调用get(),在这种情况下,get()将阻塞,直至结果准备就绪。
5、休眠
调用sleep()方法,将使任务中止执行给定的时间。例如:TimeUnit.MILLISECONDS.sleep(100); // 休眠100毫秒
6、优先级
线程的优先级将该线程的重要性传递给调度器。调度器将倾向于让优先权最高的线程先执行。优先级较低的线程执行的频率较低。
注:在绝大多数时间里,所有线程都应该以默认的优先级运行。试图操纵线程优先级通常是一种错误。
使用getPriority()来读取现有线程的优先级,通过setPriority()来修改它。
例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimplePriorities implements Runnable {
private int countDown = 5;
private volatile double d; // No optimization
private int priority;
public SimplePriorities(int priority) {
this.priority = priority;
}
public String toString() {
return Thread.currentThread() + ": " + countDown;
}
public void run() {
Thread.currentThread().setPriority(priority);
while (true) {
// An expensive, interrupeable operation
for (int i=1; i<10000; i++) {
d += (Math.PI + Math.E) / (double)i;
if (i % 1000 == 0) {
Thread.yield();
}
}
System.out.println(this);
if (--countDown == 0) return;
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i=0; i<5; i++) {
exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
}
exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
exec.shutdown();
}
}
注:尽管JDK有10个优先级,但它与多数操作系统都不能映射得很好,唯一可移植的方法是当调用调整优先级的时候,只使用MAX_PRIORITY、NORM_PRIORITY和MIN_PRIORITY三种级别。
7、让步
如果知道已经完成了在run()方法的循环的一次迭代过程中所需的工作,就可以给线程调度机制一个暗示:你的工作已经做得差不多了,可以让别的线程使用CPU了,该暗示通过Thread.yield()方法来进行(注意,这只是一个暗示,没有任何机制保证它将会被采纳)。
8、后台线程
所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。特别注意,当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。
创建后台线程,需要在线程启动之前调用setDaemon()方法,才能把该线程设置为后台线程。
如果一个线程是后台线程,那么它创建的任何线程将被自动设置为后台线程。通过isDaemon()方法可以确定一个线程是否是一个后台线程。
import java.util.concurrent.TimeUnit;
public class SimpleDaemons implements Runnable {
public void run() {
try {
while (true) {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread() + " " + this);
}
} catch (InterruptedException e) {
System.out.println("sleep() intrrupted");
}
}
public static void main(String[] args) throws InterruptedException {
for (int i=0; i<10; i++) {
Thread daemon = new Thread(new SimpleDaemons());
daemon.setDaemon(true); // Must call before start()
daemon.start();
}
System.out.println("All daemons started");
TimeUnit.MILLISECONDS.sleep(175);
}
}
另外需要注意的一点,后台线程被杀死时,是突然终止的。所以finally子句也不一定会执行。例如:
import java.util.concurrent.TimeUnit;
public class ADaemon implements Runnable {
public void run() {
try {
System.out.println("Starting ADaemon");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("Exiting via InterruptedException");
} finally {
System.out.println("This should always run?");
}
}
public static void main(String[] args) {
Thread t = new Thread(new ADaemon());
t.setDaemon(true);
t.start();
}
}
运行结果是:
Starting ADaemon
可以看出finally子句并没有执行,因为一旦main()退出,JVM就会立即关闭所有的后台进程。后台进程的结束方式并不优雅,非后台的Executor通常是一种更好的方式。
9、加入一个线程
如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(即t.isAlive()返回false)。也可以在调用join()时带上一个超时参数(单位可以是毫秒,或者纳秒),这样如果目标线程在这段时间到期时还没有借宿的话,join()方法总能返回。
对join()方法的调用可以被中断,做法是在调用线程上调用interrupt()方法。
10.创建有响应的用户界面
11、线程组
线程组持有一个线程集合,“最好把线程组看成是一次不成功的尝试,你只要忽略它就好了。”
12、捕获异常
由于线程的本质特性,使得我们不能捕获从线程中逃逸的异常,除非采用了特殊的步骤捕获这种异常。在Java SE5之前,可以通过线程组来捕获这些异常,但是在Java SE5之后,可以使用Executor来解决这个问题。
下面代码,显示一个总是抛出异常的任务,该异常会被抛到run()方法之外,显示为未捕获的异常。
public class ExceptionThread implements Runnable {
public void run() {
throw new RuntimeException();
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
}
}
即使将上述代码的main的主体放到try-catch语句块中也是没有作用的。
为了解决这个问题,我们要修改Executor产生线程的方式。Thread.UncaughtExceptionHandler是Java SE5中的新接口,它允许在每个Thread对象上都附加一个异常处理器,Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用。
例如,下面的例子创建了一个新类型的ThreadFactory,它将在每个新创建的Thread对象上附着一个Thread.UncaughtExceptionHandler.
class ExceptionThread2 implements Runnable {
public void run() {
Thread t = Thread.currentThread();
System.out.println("run() by " + t);
System.out.println("eh = " + t.getUncaughtExceptionHandler());
throw new RuntimeException();
}
}
class MyUncaughExceptionHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught " + e);
}
}
class HandlerThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
System.out.println(this + " creating new Thread");
Thread t = new Thread(r);
System.out.println("created " + t);
t.setUncaughtExceptionHandler(new MyUncaughExceptionHandler());
System.out.println("eh = " + t.getUncaughtExceptionHandler());
return t;
}
}
public class CaptureUncaughtException {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool( new HandlerThreadFactory());
exec.execute(new ExceptionThread2());
}
}
运行上面的代码,未捕获的异常是通过uncaughtException来捕获的。
三、共享受限资源
有了并发就可以同时做多件事情了,但是,两个或多个线程彼此相互干涉的问题也就出现了。
1、不正确得访问资源
Java中,递增不是原子性的操作。因此,如果不保护任务,即使单一的递增也不是安全的。
2、解决共享资源竞争
防止共享资源冲突的方法就是当资源被一个任务使用时,在其上加锁。基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案。这意味着在给定时刻只允许一个任务访问共享资源。
通常这是通过在代码前面加上一条锁语句来实现的,这使得在一段时间内只有一个任务可以运行这段代码。因为锁语句产生了一种互斥的效果,所以这种机制常常被称为互斥量(mutex)。
Java 提供了关键字synchronized,为防止资源冲突提供了内置支持。
把方法标记为synchronsized来防止资源冲突,下面是声明synchronized方法的方式:synchronized void f() {/*...*/}.
所有对象都自动含有单一的锁(也称为监视器)。对于某个特定对象来说,其所有synchronized方法共享同一个锁,这可以被用来防止多个任务同时访问被编码为对象内存。
一个任务可以多次获得对象的锁,JVM负责跟踪对象被加锁的次数。只有首先获得了锁的任务才能允许继续获取多个锁,每当任务离开一个synchronized方法,计数递减,当计数为零时,锁被完全释放,此时别的任务就可以使用此资源。
对于每个类,也有一个锁(作为类的Class对象的一部分),所以synchronized static方法可以在类的范围内防止对static数据的并发访问。
特别注意:如果在你的类中有超过一个方法在处理临界数据,那么你必须同步所有相关的方法。
示例:
public class SynchronizedEvenGenerator extends IntGenerator {
private int currentEvenValue = 0;
public synchronized int next() {
++currentEvenValue;
Thread.yield();
++currentEvenValue;
return currentEvenValue;
}
public static void main(String[] args) {
EvenChecker.test(new SynchronizedEvenGenerator());
}
}
<2>、 使用显式的Lock对象
Java SE5的java.util.concurrent类库中包含有定义在java.util.concurrent.locks中的显式的互斥机制。Lock对象必须被显式地创建、锁定和释放。因此,它与内建的锁形式相比,代码缺乏有雅性,但是对于解决某些类型的问题来说,它更加灵活。
例子:
public class MutexEvenGenerator extends IntGenerator {
private int currentEvenValue = 0;
private Lock lock = new ReentrantLock();
public int next() {
lock.lock();
try {
++currentEvenValue;
Thread.yield();
++currentEvenValue;
return currentEvenValue;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
EvenChecker.test(new MutexEvenGenerator());
}
}
通常当你使用synchronized关键字时,需要写的代码量更少,并且用户错误出现的可能性也会降低,因此通常只要在解决特殊问题时,才使用显式的Lock对象。
3、原子性与易变性
原子操作是不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在可能发生的“上下文切换”之前执行完毕。同步不应该依赖于原子性操作,依赖于原子性是很棘手且很危险的。
原子性可以应用于除long和double之外的所有基本类型之上的“简单操作”。不能保证对long和double类型的原子操作是因为:JVM将64位(long和double变量)的读取和写入当作两个分离的32位操作来执行,这就产生了在一个读取和写入操作中间发生上下文切花,从而导致不同的任务可以看到不正确结果的可能性。
解决long和double操作的原子性的方法是:将long和double变量声明时使用volative关键字。
可视性问题,可视性是指在一个任务中进行的操作结果对于不同的任务是否可见。在多处理器系统上,一个任务做出的修改,即使在不中断的意义上讲是原子性的,对其他任务也可能是不可视的(例如,修改只是暂时性地存储在本地处理器的缓存中)。
同步机制强制在处理器系统中,一个任务做出的修改必须在应用中是可视的。volative关键字也确保了应用中的可视性。如果将一个域声明为volatile的,那么只要对这个域产生了写操作,那么所有的读操作就都可以看到这个修改。
注:如果多个任务在同时访问某个域,那么这个域就应该是volatile的,否则,这个域就应该只能经由同步来访问。
4、原子类
java SE5中引入了诸如AtomicInteger、AtomicLong、AtomicReference等特殊的原子性变量类,他们提供下面形式的原子性条件更新操作:
boolean compareAndSet(expectedValue, updateValue); 这些类在常规编程上,很少用到,但是在涉及到性能调优时,就会大有用处。
public class AtomicIntegerTest implements Runnable {
private AtomicInteger i = new AtomicInteger(0);
public int getValue() { return i.get(); }
private void evenIncrement() { i.addAndGet(2); }
public void run() {
while(true) {
evenIncrement();
}
}
public static void main(String[] args) {
new Timer().schedule(new TimerTask() {
public void run() {
System.err.println("Aborting");
System.exit(0);
}
}, 5000);
ExecutorService exec = Executors.newCachedThreadPool();
AtomicIntegerTest ait = new AtomicIntegerTest();
exec.execute(ait);
while (true) {
int val = ait.getValue();
if (val % 2 != 0) {
System.out.println(val);
System.exit(0);
}
}
}
}
注:应该强调的是,Atomic类设计用来构建java.util.concurrent中的类,因此只有在特殊情况下才在自己的代码中使用它们。
5、临界区
如果只希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法。通过这种方式分离出来的代码段被称为临界区(critical section),它也使用synchronized关键字建立。synchronized被用来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制:
synchronized(syncObject) {
// This code can be accessed
// by only one task at a time
}
注:这也被称为同步控制块,在进入此段代码前,必须得到syncObject对象的锁。如果其他线程已经得到这个锁,那么就得等到锁被释放以后,才能进入临界区。
通过使用同步控制块,而不是对整个方法进行同步控制,可以使多个任务访问对象的时间性能得到显著提高。
使用显式的Lock对象来创建临界区:
class ExplicitPairManager {
private Lock lock = new ReentrantLock();
int data = 0;
public void increment() {
int temp = 0;
lock.lock();
try {
data++;
data++;
temp = data;
} finally {
lock.unlock();
}
System.out.println(temp);
}
}
6、在其他对象上同步
使用synchronized块进行同步时,必须给定一个在其上进行同步的对象,例如使用其方法正在被调用的当前对象:synchronized(this) ,如果获得了synchronized块的锁,那么该同步对象其他的synchronized方法和临界区就不能被调用了。
同样可以让synchronized块在另外的对象上同步,这样只要synchronized块同步的对象不同,那么synchronized块就不会相互关联。
例如:
public 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 static void main(String[] args) {
final DualSynch ds = new DualSynch();
new Thread() {
public void run() {
ds.f();
}
}.start();
ds.g();
}
}
7、线程本地存储
防止任务在共享资源上产生冲突的第二种方式是:根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。
例如:
class Accessor implements Runnable {
private final int id;
public Accessor(int idn) {
id = idn;
}
public void run() {
while(!Thread.currentThread().isInterrupted()) {
ThreadLocalVariableHolder.increment();
System.out.println(this);
Thread.yield();
}
}
public String toString() {
return "#" + id + ": " + ThreadLocalVariableHolder.get();
}
}
public class ThreadLocalVariableHolder {
private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
private Random rand = new Random(47);
protected synchronized Integer initialValue() {
return rand.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 Exception {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i=0; i<5; i++) {
exec.execute(new Accessor(i));
}
TimeUnit.SECONDS.sleep(3);
exec.shutdownNow();
}
}
创建和管理线程本地存储可以由java.lang.ThreadLocal类来实现,Threadlocal对象通常当作静态域存储。在创建ThreadLocal时,你只能通过get()和set()方法来访问该对象的内容。 ThreadLocal保证不会出现竞争条件。
四、终结任务
1、线程的状态
一个线程可以处于一下四种状态之一:
<1>、新建(new) :当线程被创建时,它只会短暂地处于这种状态。此时它已经分配了必需的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将把这个线程转变为可运行状态或阻塞状态
<2>、就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。也就是说,在任意时刻,线程可以运行也可以不运行。只要调度器能分配时间片给线程。它就可以运行。这不同于死亡和阻塞状态。
<3>、阻塞(Blocked):线程能够运行,但有某个条件阻止它的运行。当线程处于阻塞状态时,调度器将忽略线程。不会分配给线程任何CPU时间。直到线程重新进入了就绪状态。它才有可能执行操作。
<4>、死亡(Dead):处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到CPU时间,它的任务已结束。或不再是可运行的。任务死亡的通常方式是从run()方法返回,但是任务的线程还可以被中断。
2、在阻塞时终结
任务进入阻塞状态的原因:可能如下:
<1>、通过调用sleep(millseconds)使任务进入休眠状态,在这种情况下,任务在指定的时间内不会进行。
<2>、通过调用wait()使线程挂起。直到线程得到了notify()或notifyAll()消息(或者在Java SE5的java.util.concurrent类库中等价的signal()或signalAll()消息),线程才会进入就绪状态。
<3>、任务在等待某个输入或输出完成
<4>、任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获取了这个锁
suspend()和resume()可以来阻塞和唤醒线程,但是在现代java中这些方法被废止了(因为可能导致死锁)。stop()方法也被废止了,因为它不释放线程获得的锁。
如果希望终止处于阻塞状态的任务,那么就必须强制这个任务跳出阻塞状态。
3、中断
Thread.interrupted()提供了离开任务run()循环而不抛出异常的方式。
新的concurrent类库在避免对Thread对象的直接操作,如果调用Executor上的shutdownNow(),那么它将发送一个interrupt()调用给它启动的所有线程。cancel()是一种中断由Executor启动的单个线程的方式。
例如;
class SleepBlocked implements Runnable {
public void run() {
try {
TimeUnit.SECONDS.sleep(100);
} catch(InterruptedException e) {
System.out.println("InterruptedException");
}
System.out.println("Exiting SleepBlocked.run()");
}
}
class IOBlocked implements Runnable {
private InputStream in;
public IOBlocked(InputStream is) {
in = is;
}
public void run() {
try {
System.out.println("Waiting for read():");
in.read();
} catch(IOException e) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted from blocked I/O");
} else {
throw new RuntimeException(e);
}
}
System.out.println("Exiting IOBlocked.run()");
}
}
class SynchronizedBlocked implements Runnable {
public synchronized void f() {
while (true) {
Thread.yield();
}
}
public SynchronizedBlocked() {
new Thread() {
public void run() {
f();
}
}.start();
}
public void run() {
System.out.println("Trying to call f()");
f();
System.out.println("Exiting SynchronizedBlocked.run()");
}
}
public class Interrupting {
private static ExecutorService exec = Executors.newCachedThreadPool();
static void test(Runnable r) throws InterruptedException {
Future<?> f = exec.submit(r);
TimeUnit.MILLISECONDS.sleep(100);
System.out.println("Intettupting " + r.getClass().getName());
f.cancel(true);
System.out.println("Interrupt sent to " + r.getClass().getName());
System.exit(0);
}
public static void main(String[] args) throws Exception {
test(new SleepBlocked());
test(new IOBlocked(System.in));
test(new SynchronizedBlocked());
TimeUnit.SECONDS.sleep(3);
System.out.println("Aborting with System.exit(0)");
}
}
上例中每个任务都表示一种不同类型的阻塞。SleepBlock是可中断的阻塞示例。而IOBlocked和SynchronizedBlocked是不可中断的阻塞示例。可以看出可以中断对sleep()的调用(或者任何要求抛出InterruptedException的调用),但是不能中断正在试图获取synchronized锁或者试图执行I/O操作的线程。
对于试图获取synchronized锁或者执行I/O操作的线程不能被中断的解决方法是:关闭任务在其上发生阻塞的底层资源,一旦底层资源被关闭,任务将解除阻塞。
对于被互斥所阻塞的线程是可以中断的。(在ReentrantLock上阻塞的任务具备可以被中断的能力)。
4、检查中断
可以通过调用interrupted()来检查中断状态。这不仅可以获知interrupt()是否被调用过,而且还可以清除中断状态。
例如:下面是检查中断的典型惯用法:
class NeedsCleanup {
private final int id;
public NeedsCleanup(int ident) {
id = ident;
System.out.println("NeedsCleanup " + id);
}
public void cleanup() {
System.out.println("Cleaning up " + id);
}
}
class Blocked3 implements Runnable {
private volatile double d = 0.0;
public void run() {
try {
while (!Thread.interrupted()) {
// point1
NeedsCleanup n1 = new NeedsCleanup(1);
try {
System.out.println("Sleeping");
TimeUnit.SECONDS.sleep(1);
// point2
NeedsCleanup n2 = new NeedsCleanup(2);
// Guarantee proper cleanup of n2
try {
System.out.println("Calcuting");
for (int i=1; i<2500000; i++) {
d = d + (Math.PI + Math.E) / d;
}
System.out.println("Finished time-consuming operation");
} finally {
n2.cleanup();
}
} finally {
n1.cleanup();
}
}
System.out.println("Exiting via while() test");
} catch (InterruptedException e) {
System.out.println("Exiting via InterruptedException");
}
}
}
public class InterruptingIdiom {
public static void main(String[] args) throws Exception {
Thread t = new Thread(new Blocked3());
t.start();
TimeUnit.MILLISECONDS.sleep(new Integer("100"));
t.interrupt();
}
}
NeedsCleanup类强调在你经由异常离开循环时,正确清理资源的必要性。
五、线程间协作
多线程同步问题既有多线程对资源的竞争问题,也有多线程的协同工作问题。前者是指对共享资源的访问,后者是值任务之间的握手(响应信号)。
1、wait()与notifyAll()
wait()方法等待某个条件发生变化,通常这种条件将由另一个任务来改变。wait()会在等待外部产生变化时将任务挂起,并且只有在notify()或notifyAll()发生时,这个任务才会被唤醒并去检查所产生的变化。
对于sleep()方法调用的时候锁并没有被释放,而调用wait()而言:
** 在wait()期间对象锁是释放的
** 可以通过notify()、notifyAll(),或者令超时时间到期,从wait()中恢复执行
注意:wait().notify()以及notifyAll()都是基类Object的一部分,而不是属于Thread的一部分。但是实际上,只能在同步控制方法或同步控制块里调用wait()、notify()和notifyAll(),如果在非同步控制方法里调用这些方法,程序能编译通过,但是运行时将得到IllegalMonitorStateException异常,试图调用wait()、notify()和notifyAll的任务在调用这些方法前必须“拥有”(获取)对象的锁。
下面的例子演示了:一个将蜡涂到Car上,一个是抛光它。抛光任务在涂蜡任务完成之前,是不能执行其工作的,而涂蜡任务在涂另一层蜡之前,必须等待抛光任务完成。
class Car {
private boolean waxOn = false;
public synchronized void waxed() {
waxOn = true; // Ready to buff
notifyAll();
}
public synchronized void buffed() {
waxOn = false; // Ready for anther coat of wax
notifyAll();
}
public synchronized void waitForWaxing() throws InterruptedException {
while (waxOn == false) {
wait();
}
}
public synchronized void waitForBuffing() throws InterruptedException {
while (waxOn == true) {
wait();
}
}
}
class WaxOn implements Runnable {
private Car car;
public WaxOn(Car c) {
car = c;
}
public void run() {
try {
while (!Thread.interrupted()) {
System.out.println("Wax On! ");
TimeUnit.MICROSECONDS.sleep(200);
car.waxed();
car.waitForBuffing();
}
} catch (InterruptedException e) {
System.out.println("Exiting via interrupt");
}
System.out.println("Ending Wax On task");
}
}
class WaxOff implements Runnable {
private Car car;
public WaxOff(Car c) {
car = c;
}
public void run() {
try {
while (!Thread.interrupted()) {
car.waitForWaxing();
System.out.println("Wax Off! ");
TimeUnit.MILLISECONDS.sleep(200);
car.buffed();
}
} catch (InterruptedException e) {
System.out.println("Exiting via interrupt");
}
System.out.println("Ending Wax Off task");
}
}
public class WaxOMatic {
public static void main(String[] args) throws InterruptedException {
Car car = new Car();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new WaxOff(car));
exec.execute(new WaxOn(car));
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow(); // Interrupted all tasks
}
}
上例中需要强调的一点是:用while循环包围wait()。因为:
*** 你可能有多个任务处于相同的原因在等待同一个锁,而第一个唤醒任务可能会改变这种状态,如果是这种情况,那么这个任务应该被再次挂起,直至其感兴趣的条件发生变化
*** 在这个任务从其wait()中唤醒的时刻,有可能会有某个其他的任务已经做出了改变,从而使得这个任务在此时不能执行。或者执行其操作已显得无关紧要,此时,应该通过再次调用wait()来将其重新挂起
*** 也有可能某些任务处于不同的原因在等待你的对象上的锁(在这种情况下必须使用notifyAll())。在这种情况下,你需要检查是否已经由正确的原因唤醒,如果不是,就再次调用wait()。
综上,当要检查所感兴趣的特定条件,并在条件不满足的情况下返回到wait()中,惯用做法是使用while循环来编写这种代码。
<> 可能出现信号丢失的情况
两个线程使用notify()/wait()或notifyAll()/wait()进行协作时,有可能会错过某个信号。假设T1是通知T2的线程,而这两个线程都是使用下面(有缺陷的)方式实现的:
T1:
Synchronized(sharedMonitor) {
<setup condition for T2>
shareMonitor.notify();
}
T2:
while (someCondition) {
// Point 1
synchronized(shareMonitor) {
sharedMonitor.wait();
}
}
<Setup condition for T2>是防止T2调用wait()的一个动作。假设T2对someCondition求值并发现其为true。当线程调度器切换到T1,而T1将执行其设置,然后调用notify()。当T2得以继续执行时,此时对于T2来说,时机已经太晚了,以至于不能意识到这个条件已经发生了变化,因此会盲目进入wait()。此时notify()将错失,而T2也将无限地等待这个已经发送过的信号,从而产生死锁。
该问题的解决方法是防止在someCondition变量上产生竞争条件,下面是T2正确的执行方式:
synchronized(sharedMonitor) {
while (someCondition) {
sharedMonitor.wait();
}
}
这样,如果T1首先执行,当控制返回T2时,它将发现条件发生了变化。从而不会进入wait()。反过来,如果T2首先执行,那么它将进入wait(),并且稍后会由T1唤醒,因此,信号不会错失。
2、notify()与notifyAll()
使用notify()时,会在众多等待同一个锁的任务中只有一个会被唤醒。notifyAll()会使得所有正在等待同一个锁的任务。
3、生产者与消费者
下例演示了一个模拟程序,一个厨师和一个服务员,厨师代表生产者、服务员代表消费,两个任务必须啊在事务被生产和消费时进行握手,而系统必须以有序的方式关闭。
例如:
class Meal {
private final int orderNum;
public Meal(int orderNum) {
this.orderNum = orderNum;
}
public String toString() {
return "Meal " + orderNum;
}
}
class WaitPerson implements Runnable {
private Restaurant restaurant;
public WaitPerson(Restaurant r) {
restaurant = r;
}
public void run() {
try {
while (!Thread.interrupted()) {
synchronized(this) {
while (restaurant.meal == null) {
wait(); // ... for the chef to produce a meal
}
}
System.out.println("Waitperson got " + restaurant.meal);
synchronized(restaurant.chef) {
restaurant.meal = null;
restaurant.chef.notifyAll(); // Ready for another
}
}
} catch (InterruptedException e) {
System.out.println("WaitPerson interrupted.");
}
}
}
class Chef implements Runnable {
private Restaurant restaurant;
private int count = 0;
public Chef(Restaurant r) {
restaurant = r;
}
public void run() {
try {
while (!Thread.interrupted()) {
synchronized(this) {
while (restaurant.meal != null)
wait(); // ... for the meal to be taken
}
if (++count == 10) {
System.out.println("Out of food , closing");
restaurant.exec.shutdownNow();
}
System.out.println("Order up! ");
synchronized(restaurant.waitPerson) {
restaurant.meal = new Meal(count);
restaurant.waitPerson.notifyAll();
}
TimeUnit.MILLISECONDS.sleep(100);
}
} catch (InterruptedException e) {
System.out.println("Chef interrupted");
}
}
}
public class Restaurant {
Meal meal;
ExecutorService exec = Executors.newCachedThreadPool();
WaitPerson waitPerson = new WaitPerson(this);
Chef chef = new Chef(this);
public Restaurant() {
exec.execute(chef);
exec.execute(waitPerson);
}
public static void main(String[] args) {
new Restaurant();
}
}
使用显式的Lock和Condition对象
Condition类,可以实现这样的功能,调用Condition.await()可以挂起一个任务,调用signal()或signalAll()可以唤醒在这个Condition上被其自身挂起的任务。
4、生产者-消费者与队列
wait()和nitifuAll()方法是以一种非常低级的方式解决任务互操作的问题,即每次交互时都握手。更高级的做法是:使用同步队列来解决任务协作问题。同步队列在任何时刻都只允许一个任务插入或移除元素。LinkedBlockingQueue是一个无界的队列,ArrayBlockingQueue是一个具有固定尺寸的同步队列。
下面例子展示了同步队列用法:
class LiftOffRunner implements Runnable {
private BlockingQueue<LiftOff> rockets;
public LiftOffRunner(BlockingQueue<LiftOff> queue) {
rockets = queue;
}
public void add(LiftOff lo) {
try {
rockets.put(lo);
} catch (InterruptedException e) {
System.out.println("Interrupted during put()");
}
}
public void run() {
try {
while (!Thread.interrupted()) {
LiftOff rocket = rockets.take();
rocket.run();
}
} catch (InterruptedException e) {
System.out.println("Waking from take()");
}
System.out.println("Exiting LiftOffRunner");
}
}
public class TestBlockingQueues {
static void getkey() {
try {
new BufferedReader(new InputStreamReader(System.in)).readLine();
} catch (java.io.IOException e) {
throw new RuntimeException(e);
}
}
static void getkey(String message) {
System.out.println(message);
getkey();
}
static void test(String msg, BlockingQueue<LiftOff> queue) {
System.out.println(msg);
LiftOffRunner runner = new LiftOffRunner(queue);
Thread t = new Thread(runner);
t.start();
for (int i=0; i<5; i++) {
runner.add(new LiftOff(5));
}
getkey("Press 'Enter' (" + msg + ")");
t.interrupted();
System.out.println("Finished " + msg + " test");
}
public static void main(String[] args) {
test("LinkedBlockingQueue",
new LinkedBlockingQueue<LiftOff>());
test("ArrayBlockingQueue",
new ArrayBlockingQueue<LiftOff>(3));
test("SynchronousQueue",
new SynchronousQueue<LiftOff>());
}
}
下面示例显示了:有一台机器具有三个任务:一个制作寿司,一个给寿司涂黄油,另一个给涂过黄油的寿司涂果酱。
class Toast {
public enum Status {DRY, BUTTERED, JAMMED}
private Status status = Status.DRY;
private final int id;
public Toast(int idn) { id = idn; }
public void butter() { status = Status.BUTTERED; }
public void jam() { status = Status.JAMMED; }
public Status getStatus() { return status; }
public int getId() { return id; }
public String toString() {
return "Toast " + id + ": " + status;
}
}
class ToastQueue extends LinkedBlockingQueue<Toast> {}
class Toaster implements Runnable {
private ToastQueue toastQueue;
private int count = 0;
private Random rand = new Random(47);
public Toaster(ToastQueue tq) {
toastQueue = tq;
}
public void run() {
try {
while (!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(100 + rand.nextInt(500));
Toast t = new Toast(count++);
System.out.println(t);
toastQueue.put(t);
}
} catch (InterruptedException e) {
System.out.println("Toaster interrupted");
}
System.out.println("Toaster off");
}
}
// Apply butter to toast:
class Butterer implements Runnable {
private ToastQueue dryQueue, butteredQueue;
public Butterer(ToastQueue dry, ToastQueue buttered) {
dryQueue = dry;
butteredQueue = buttered;
}
public void run() {
try {
while (!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = dryQueue.take();
t.butter();
System.out.println(t);
butteredQueue.put(t);
}
} catch (InterruptedException e) {
System.out.println("Butterer interrupted");
}
System.out.println("Butterer off");
}
}
// Apply jam to buttered toast:
class Jammer implements Runnable {
private ToastQueue butteredQueue, finishedQueue;
public Jammer(ToastQueue buttered, ToastQueue finished) {
butteredQueue = buttered;
finishedQueue = finished;
}
public void run() {
try {
while (!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = butteredQueue.take();
t.jam();
System.out.println(t);
finishedQueue.put(t);
}
} catch (InterruptedException e) {
System.out.println("Jammer interrupted");
}
System.out.println("Jammer off");
}
}
// Consume the toast:
class Eater implements Runnable {
private ToastQueue finishedQueue;
private int counter = 0;
public Eater(ToastQueue finished) {
finishedQueue = finished;
}
public void run() {
try {
while (!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = finishedQueue.take();
if (t.getId() != counter++ ||
t.getStatus() != Toast.Status.JAMMED) {
System.out.println(">>>> Error: " + t);
System.exit(1);
} else {
System.out.println("Chomp! " + t);
}
}
} catch (InterruptedException e) {
System.out.println("Eater interrupted");
}
}
}
public class ToastOMatic {
public static void main(String[] args) throws Exception {
ToastQueue dryQueue = new ToastQueue(),
butteredQueue = new ToastQueue(),
finishedQueue = new ToastQueue();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Toaster(dryQueue));
exec.execute(new Butterer(dryQueue, butteredQueue));
exec.execute(new Jammer(butteredQueue, finishedQueue));
exec.execute(new Eater(finishedQueue));
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow();
}
}
上例中没有任何显式的同步(即使用Lock对象或synchronized关键字的同步),因为同步由队列(其内部是同步的)和系统的设计隐式地管理了,所以上述程序显得很简单。
5、任务间使用管道进行输入/输出
通过输入/输出在线程间进行通信通常很有用。java类库提供“管道”的形式对线程间的输入/输出提供了支持。对应的类是PipedWriter类(允许任务向管道中写)和PipedReader类(运行不同任务从同一个管道中读取)。管道是一种“生产者-消费者”问题的变体,是一个封装好的解决方案。
下例展示了两个任务使用一个管道进行通信:
class Sender implements Runnable {
private Random rand = new Random(47);
private PipedWriter out = new PipedWriter();
public PipedWriter getPipedWriter() {
return out;
}
public void run() {
try {
while (true) {
for (char c = 'A'; c <= 'z'; c++) {
out.write(c);
System.out.print("Write:" + c + " ");
TimeUnit.MILLISECONDS.sleep(rand.nextInt(500));
}
}
} catch(IOException e) {
System.out.println(e + " Sender write exception");
} catch(InterruptedException e) {
System.out.println(e + " Sender sleep interrupted");
}
}
}
class Receiver implements Runnable {
private PipedReader in;
public Receiver(Sender sender) throws IOException {
//in = new PipedReader(sender.getPipedWriter());
in = new PipedReader();
in.connect(sender.getPipedWriter());
}
public void run() {
try {
while (true) {
// Blocks until characters are there:
System.out.println("Read: " + (char)in.read() + ".");
}
} catch(IOException e) {
System.out.println(e + " Receiver read exception");
}
}
}
public class PipedIO {
public static void main(String[] args) throws Exception {
Sender sender = new Sender();
Receiver receiver = new Receiver(sender);
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(sender);
exec.execute(receiver);
TimeUnit.SECONDS.sleep(4);
exec.shutdownNow();
}
}
六、死锁
任务之间相互等待资源,而导致的所有任务都阻塞的现象。在编写并发程序的时候,进行仔细的程序设计以防止死锁是关键部分。Java对死锁并没有提供语言层面的支持,能够通过仔细地设计程序来避免死锁,这取决于你自己。
七、新类库中的构件
1、CountDownLatch
CountDownLatch被用来同步一个或多个任务,强制它们等待由其他任务执行的一组操作完成。任何在这个对象上调用await()的方法都将阻塞,直至这个计数值到达0。(注意调用await()的任务是一直被阻塞,直到计数值为0才唤醒)。
下例演示了CountDownLatch的用法:
// Performs some portion of a task:
class TaskPortion implements Runnable {
private static int counter = 0;
private final int id = counter++;
private static Random rand = new Random(47);
private final CountDownLatch latch;
TaskPortion(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
doWork();
latch.countDown();
} catch (InterruptedException ex) {
// Acceptable way to exit
}
}
public void doWork() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(rand.nextInt(2000));
System.out.println(this + "completed");
}
public String toString() {
return String.format("%1$-3d ", id);
}
}
// Waits on the CountDownLatch
class WaitingTask implements Runnable {
private static int counter = 0;
private final int id = counter++;
private final CountDownLatch latch;
WaitingTask(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
latch.await();
System.out.println("Latch barrier passed for " + this);
} catch (InterruptedException ex) {
System.out.println(this + " interrupted");
}
}
public String toString() {
return String.format("WaitingTask %1$-3d ", id);
}
}
public class CountDownLatchDemo {
static final int SIZE = 100;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
// All must share a single CountDownLatch object:
CountDownLatch latch = new CountDownLatch(SIZE);
for (int i=0; i<10; i++) {
exec.execute(new WaitingTask(latch));
}
for (int i=0; i<SIZE; i++) {
exec.execute(new TaskPortion(latch));
}
System.out.println("Launched all tasks");
exec.shutdown(); // Quit when all tasks complete
}
}
TaskPortion将随机地休眠一段时间,以模仿这部分工作的完成,而WaitingTask表示系统中必须等待的部分,它要等待到问题的初始部分完成为止。所有任务都使用了在main()中定义的同一个单一的CountDownLatch。
2、CyclicBarrier
如果你希望创建一组任务,他们并行地执行工作,然后在进行下一个步骤之前等待,直至所有任务都完成。这非常像CountDownLatch,只是CountDownLatch是只触发一次的事件,而CyclicBarrier可以多次重用。
下例是一个赛马游戏的多线程版本:
class Horse implements Runnable {
private static int counter = 0;
private final int id = counter++;
private int strides = 0;
private static Random rand = new Random(47);
private static CyclicBarrier barrier;
public Horse(CyclicBarrier b) {
barrier = b;
}
public synchronized int getStrides() {
return strides;
}
public void run() {
try {
while (!Thread.interrupted()) {
synchronized(this) {
strides += rand.nextInt(3); // Produces 0, 1, or 2
}
barrier.await();
}
} catch (InterruptedException e) {
// A legitimate way to exit
} catch (BrokenBarrierException e) {
// This one we want to know about
throw new RuntimeException(e);
}
}
public String toString() {
return "Horse " + id + " ";
}
public String tracks() {
StringBuilder s = new StringBuilder();
for (int i=0; i<getStrides(); i++) {
s.append("*");
}
s.append(id);
return s.toString();
}
}
public class HorseRace {
static final int FINISH_LINE = 75;
private List<Horse> horses = new ArrayList<Horse>();
private ExecutorService exec = Executors.newCachedThreadPool();
private CyclicBarrier barrier;
public HorseRace(int nHorses, final int pause) {
barrier = new CyclicBarrier(nHorses, new Runnable() {
public void run() {
StringBuilder s = new StringBuilder();
for (int i=0; i<FINISH_LINE; i++) {
s.append("="); // The fence on the racetrack
}
System.out.println(s);
for (Horse horse : horses) {
System.out.println(horse.tracks());
}
for (Horse horse : horses) {
if (horse.getStrides() >= FINISH_LINE) {
System.out.println(horse + "won!");
exec.shutdownNow();
return;
}
}
try {
TimeUnit.MILLISECONDS.sleep(pause);
} catch (InterruptedException e) {
System.out.println("barrier-action sleep interrupted");
}
}
});
for (int i=0; i<nHorses; i++) {
Horse horse = new Horse(barrier);
horses.add(horse);
exec.execute(horse);
}
}
public static void main(String[] args) {
int nHorses = 7;
int pause = 200;
if (args.length > 0) {
int n = new Integer(args[0]);
nHorses = n > 0 ? n : nHorses;
}
if (args.length > 1) {
int p = new Integer(args[1]);
pause = p > -1 ? p : pause;
}
new HorseRace(nHorses, pause);
}
}
注:可以向CyclicBarrier提供一个“栅栏动作”,它是一个Runnable,当计数值到达0时自动执行——这是CyclicBarrier和CountDownLatch之间的另一个区别。如上例所示,这里的“栅栏动作”是作为匿名内部类创建的,它被提交给了CyclicBarrier的构造器。
3、DelayQueue
DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,值得注意的是:该队列中的对象只能在其到期时才能从队列中取走,这种队列是有序的,即队头对象的延迟到期的时间最长,如果没有任何延迟到期,那么就不会有任何头元素,并且poll()将返回null(正因为这样,你不能将null放入这种队列中)。
class DelayedTask implements Runnable, Delayed {
private static int counter = 0;
private final int id = counter++;
private final int delta;
private final long trigger;
protected static List<DelayedTask> sequence = new ArrayList<DelayedTask>();
public DelayedTask(int delayInMilliseconds) {
delta = delayInMilliseconds;
trigger = System.nanoTime() + TimeUnit.NANOSECONDS.convert(delta, TimeUnit.MILLISECONDS);
sequence.add(this);
}
public long getDelay(TimeUnit unit) {
return unit.convert(trigger - System.nanoTime(), TimeUnit.NANOSECONDS);
}
public int compareTo(Delayed arg) {
DelayedTask that = (DelayedTask)arg;
if (trigger < that.trigger) return -1;
if (trigger > that.trigger) return 1;
return 0;
}
public void run() {
System.out.println(this + " ");
}
public String toString() {
return String.format("[%1$-4d]", delta) + " Task " + id;
}
public String summary() {
return "(" + id + ":" + delta + ")";
}
public static class EndSentinel extends DelayedTask {
private ExecutorService exec;
public EndSentinel(int delay, ExecutorService e) {
super(delay);
exec = e;
}
public void run() {
for (DelayedTask pt : sequence) {
System.out.print(pt.summary() + " ");
}
System.out.println();
System.out.println(this + " Calling shutdownNow()");
exec.shutdownNow();
}
}
}
class DelayedTaskConsumer implements Runnable {
private DelayQueue<DelayedTask> q;
public DelayedTaskConsumer(DelayQueue<DelayedTask> q) {
this.q = q;
}
public void run() {
try {
while (!Thread.interrupted()) {
q.take().run();
}
} catch (InterruptedException e) {
// Acceptable way to exit
}
System.out.println("Finished DelayedTaskConsumer");
}
}
public class DelayQueueDemo {
public static void main(String[] args) {
Random rand = new Random(47);
ExecutorService exec = Executors.newCachedThreadPool();
DelayQueue<DelayedTask> queue = new DelayQueue<DelayedTask>();
// Fill with tasks that have random delays:
for (int i=0; i<20; i++) {
queue.put(new DelayedTask(rand.nextInt(5000)));
}
// Set the stopping point
queue.add(new DelayedTask.EndSentinel(5000, exec));
exec.execute(new DelayedTaskConsumer(queue));
}
}
上例DelayedTaskConsumer将最“紧急”的任务(到期时间最长的任务)从队列中取出,然后运行它。
4、PriorityBlockingQueue
这是一个优先级队列,具有可阻塞的读取操作。
下例演示了PriorityBlockingQueue的用法:
class PrioritizedTask implements Runnable, Comparable<PrioritizedTask> {
private Random rand = new Random(47);
private static int counter = 0;
private final int id = counter++;
private final int priority;
protected static List<PrioritizedTask> sequence = new ArrayList<PrioritizedTask>();
public PrioritizedTask(int priority) {
this.priority = priority;
sequence.add(this);
}
public int compareTo(PrioritizedTask arg) {
return priority < arg.priority ? 1 : (priority > arg.priority ? -1 : 0);
}
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(rand.nextInt(20));
} catch (InterruptedException e) {
// Acceptable way to exit
}
System.out.println(this);
}
public String toString() {
return String.format("[%1$-3d]", priority) + " Task " + id;
}
public String summary() {
return "(" + id + ":" + priority + ")";
}
public static class EndSentinel extends PrioritizedTask {
private ExecutorService exec;
public EndSentinel(ExecutorService e) {
super(-1); // Lower priority in this program
exec = e;
}
public void run() {
int count = 0;
for (PrioritizedTask pt : sequence) {
System.out.println(pt.summary());
if (++count % 5 == 0) {
System.out.println();
}
}
System.out.println();
System.out.println(this + " Calling shutdownNow()");
exec.shutdownNow();
}
}
}
class PrioritizedTaskProducer implements Runnable {
private Random rand = new Random(47);
private Queue<Runnable> queue;
private ExecutorService exec;
public PrioritizedTaskProducer( Queue<Runnable> q, ExecutorService e) {
queue = q;
exec = e; // Used for EndSentinel
}
public void run() {
// Unbounded queue; never blocks.
// Fill it up fast with random priorities:
for (int i=0; i<20; i++) {
queue.add(new PrioritizedTask(rand.nextInt(10)));
Thread.yield();
}
// Trickle in highest-priority jobs:
try {
for (int i=0; i<10; i++) {
TimeUnit.MILLISECONDS.sleep(250);
queue.add(new PrioritizedTask(10));
}
// Add jobs, lowest priority first:
for (int i=0; i<10; i++) {
queue.add(new PrioritizedTask(i));
}
// A sentinel to stop all the tasks:
queue.add(new PrioritizedTask.EndSentinel(exec));
} catch (InterruptedException e) {
// Acceptable way to exit
}
System.out.println("Finished PrioritizedTaskProducer");
}
}
class PrioritizedTaskConsumer implements Runnable {
private PriorityBlockingQueue<Runnable> q;
public PrioritizedTaskConsumer(PriorityBlockingQueue<Runnable> q) {
this.q = q;
}
public void run() {
try {
while (!Thread.interrupted()) {
// Use current thread to run the task:
q.take().run();
}
} catch (InterruptedException e) {
// Acceptable way to exit
}
System.out.println("Finished PrioritizedTaskConsumer");
}
}
public class PriorityBlockingQueueDemo {
public static void main(String[] args) throws Exception {
Random rand = new Random(47);
ExecutorService exec = Executors.newCachedThreadPool();
PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<Runnable>();
exec.execute(new PrioritizedTaskProducer(queue, exec));
exec.execute(new PrioritizedTaskConsumer(queue));
}
}
上例中PriorityTaskProducer和PrioritizedTaskComsumer通过PriorityBlockingQueue彼此连接。因为这种队列的阻塞特性提供了所有必需的同步,所以这里不需要任何显式的同步。
5、ScheduledExecutor
ScheduledThreadPoolExecutor类可以在预订时间运行任务。schedule()方法运行一次任务,scheduleAtFixedRate():每隔规则的时间重复执行任务。
参看:http://blog.csdn.net/tsyj810883979/article/details/8481621
6、Semaphore
计数信号量允许n个任务同时访问某个资源。信号量可以看作是某个资源在向外分发使用资源的“许可证”。
下例是一个对象池的概念,它管理数量有限的对象。
public class Pool<T> {
private int size;
private List<T> items = new ArrayList<T>();
private volatile boolean[] checkedOut;
private Semaphore available;
public Pool(Class<T> classObject, int size) {
this.size = size;
checkedOut = new boolean[size];
available = new Semaphore(size, true);
// Load pool with objects that can be checked out:
for (int i=0; i<size; ++i) {
try {
// Assumes a default constructor:
items.add(classObject.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public T checkOut() throws InterruptedException {
available.acquire();
return getItem();
}
public void checkIn(T x) {
if (releaseItem(x)) {
available.release();
}
}
private synchronized T getItem() {
for(int i=0; i<size; ++i) {
if (!checkedOut[i]) {
checkedOut[i] = true;
return items.get(i);
}
}
return null; // Semphore prevents reaching here
}
private synchronized boolean releaseItem(T item) {
int index = items.indexOf(item);
if (index == -1) {
return false; // Not in the list
}
if (checkedOut[index]) {
checkedOut[index] = false;
return true;
}
return false; // Wasn't checked out
}
}
需要一个新对象时,可以调用checkOut(),并且在使用完之后,将其递交给checkIn()。在checkOut()中,如果没有任何信号量许可证可用,available将阻塞调用过程。在checkIn()中,如果被签入的对象有效,则会向信号量返回一个许可证。
7、Exchanger
Exchanger可以在两个线程之间交换数据,只能是两个线程,它不支持更多的线程之间互换数据。
例如:
class Producer implements Runnable {
List<Integer> list = new ArrayList<Integer>();
Exchanger<List<Integer>> exchanger = null;
public Producer(Exchanger<List<Integer>> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
Random rand = new Random();
for (int i=0; i<10; i++) {
list.clear();
list.add(rand.nextInt(10000));
list.add(rand.nextInt(10000));
list.add(rand.nextInt(10000));
list.add(rand.nextInt(10000));
list.add(rand.nextInt(10000));
try {
list = exchanger.exchange(list);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
List<Integer> list = new ArrayList<Integer>();
Exchanger<List<Integer>> exchanger = null;
public Consumer(Exchanger<List<Integer>> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
for(int i=0; i<10; i++) {
try {
list = exchanger.exchange(list);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print(list.get(0)+", ");
System.out.print(list.get(1)+", ");
System.out.print(list.get(2)+", ");
System.out.print(list.get(3)+", ");
System.out.println(list.get(4)+", ");
}
}
}
public class ExchangerDemo {
public static void main(String[] args) throws InterruptedException {
Exchanger<List<Integer>> exchanger = new Exchanger<List<Integer>>();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Producer(exchanger));
exec.execute(new Consumer(exchanger));
TimeUnit.MILLISECONDS.sleep(5000);
exec.shutdownNow();
}
}
上例中两个任务使用同一个Exchanger,Producer任务进行10次批量添加数据到List中,每次添加后交换为Consumer的List,而Consumer获得Producer填充后的List.
八、仿真
使用同步队列在任务间通信,可以极大地简化并发编程的过程:任务没有直接地相互干涉,而是经由队列相互发送对象。
九、性能调优
关于并发程序的性能调优,可以先以更加传统的互斥方式入手,只有在性能方面的需求能够明确指示时,再替换为Atomic。
2、免锁容器
Vector和Hashtable这类早期容器具有许多synchronized方法,当它们用于非多线程的应用程序时,便会导致不可接受的开销。在Java1.2中,新的容器类库是不同步的,并且Collections类提供了各种static的同步的装饰方法,从而来同步不同类型的容器(开销仍然是基于synchronized加锁机制的)。Java SE5添加了新的免锁容器,来提高线程安全的性能。
这些免锁容器的实现原理是:修改是在容器数据结构的某个部分的一个单独的副本上执行的,并且这个副本在修改过程中是不可见的。只有当修改完成时,被修改的结构才会自动地与主结构进行交换,之后读取这就可以看到这个修改了。
CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap、ConcurrentLinkedQueue ,这些方法不会抛出ConcurrentModificationException异常。
从免锁容器中读取和向免锁容器中少量写入都会比synchronized快。因为获取和释放锁的开销被省掉了。
3、乐观加锁
注:关于乐观锁和悲观锁:
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
某些Atomic类允许执行所谓的“乐观加锁”。这意味着当你执行某项计算时,实际上没有使用互斥,但是在完成计算并准备更新这个Atomic对象时,需要调用compareAndSet()方法,将旧值和新值一起提交给这个方法,如果旧值与它在Atomic对象汇总发现的值不一致,那么这个操作就失败。
4、ReadWriteLock
对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量.
ReentrantReadWriteLock会使用两把锁来解决问题,一个读锁,一个写锁
线程进入读锁的前提条件:
没有其他线程的写锁,
没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
到ReentrantReadWriteLock,首先要做的是与ReentrantLock划清界限。它和后者都是单独的实现,彼此之间没有继承或实现的关系。然后就是总结这个锁机制的特性了:
(a).重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。
(b).WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a),呵呵.
(c).ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。
(d).不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。
(e).WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。
例子:
public class ReaderWriterList<T> {
private ArrayList<T> lockedList;
// Make the ordering fair:
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
public ReaderWriterList(int size, T initialValue) {
lockedList = new ArrayList<T>(Collections.nCopies(size, initialValue));
}
public T set(int index, T element) {
Lock wlock = lock.writeLock();
wlock.lock();
try {
return lockedList.set(index, element);
} finally {
wlock.unlock();
}
}
public T get(int index) {
Lock rlock = lock.readLock();
rlock.lock();
try {
// Show that multiple readers
// may acquire the read lock:
if (lock.getReadLockCount() > 1) {
System.out.println(lock.getReadLockCount());
}
return lockedList.get(index);
} finally {
rlock.unlock();
}
}
public static void main(String[] args) throws Exception {
new ReaderWriterListTest(30, 1);
}
}
class ReaderWriterListTest {
ExecutorService exec = Executors.newCachedThreadPool();
private final static int SIZE = 100;
private static Random rand = new Random(47);
private ReaderWriterList<Integer> list = new ReaderWriterList<Integer>(SIZE, 0);
private class Writer implements Runnable {
public void run() {
try {
for (int i=0; i<20; i++) { // 2 second test
list.set(i, rand.nextInt());
TimeUnit.MILLISECONDS.sleep(100);
}
} catch (InterruptedException e) {
// Acceptable way to exit
}
System.out.println("Writer finished, shutting down");
exec.shutdownNow();
}
}
private class Reader implements Runnable {
public void run() {
try {
while (!Thread.interrupted()) {
for (int i=0; i<SIZE; i++) {
list.get(i);
TimeUnit.MILLISECONDS.sleep(1);
}
}
} catch (InterruptedException e) {
// Acceptable way to exit
}
}
}
public ReaderWriterListTest(int readers, int writers) {
for (int i=0; i<readers; i++) {
exec.execute(new Reader());
}
for (int i=0; i<writers; i++) {
exec.execute(new Writer());
}
}
}
上例中,set()方法要获取一个写锁,以调用底层的ArrayList.set(),而get()方法要获取一个读锁,以调用底层的ArrayList.get()。
十、活动对象
活动对象或行动者,是另一种不同的并发模型。每个对象都是“活动的”,每个对象都维护着它自己的工作器线程和消息队列,并且所有对这种对象的请求都将进入队列排队,任何时刻都只能运行其中的一个。
当你向一个活动对象发送消息时,这条消息会转变为一个任务,该任务会被插入到这个对象的队列中,等待在以后的某个时刻运行。
在java se5中可以用Future来实现这种模式:
public class ActiveObjectDemo {
private ExecutorService ex = Executors.newSingleThreadExecutor();
private Random rand = new Random(47);
// Insert a random delay to produce the effect
// of a calculation time:
private void pause(int factor) {
try {
TimeUnit.MILLISECONDS.sleep(100 + rand.nextInt(factor));
} catch (InterruptedException e) {
System.out.println("sleep() interrupted");
}
}
public Future<Integer> calculateInt(final int x, final int y) {
return ex.submit(new Callable<Integer>() {
public Integer call() {
System.out.println("starting " + x + " + " + y);
pause(500);
return x + y;
}
});
}
public Future<Float> calculateFloat(final float x, final float y) {
return ex.submit(new Callable<Float>() {
public Float call() {
System.out.println("starting " + x + " + " + y);
pause(2000);
return x + y;
}
});
}
public void shutdown() {
ex.shutdown();
}
public static void main(String[] args) {
ActiveObjectDemo d1 = new ActiveObjectDemo();
// Prevents ConcurrentModificationException:
List<Future<?>> results = new CopyOnWriteArrayList<Future<?>>();
for (float f = 0.0f; f < 1.0f; f += 0.2f) {
results.add(d1.calculateFloat(f, f));
}
for (int i=0; i<5; i++) {
results.add(d1.calculateInt(i, i));
}
System.out.println("All asynch calls made");
while (results.size() > 0) {
for (Future<?> f : results) {
if (f.isDone()) {
try {
System.out.println(f.get());
} catch (Exception e) {
throw new RuntimeException(e);
}
results.remove(f);
}
}
}
d1.shutdown();
}
}
上述代码中,每个活动对象方法都是通过submit()方法提交一个新的Callable对象,这样就可以把方法调用转变为消息,单一的线程ex从队列中依次取消息并执行它们。最后通过List<Future<?>>来收集执行结果。注意这个过程中消息没有任何相互干涉的地方。
关于活动对象编程领域被称为基于代理的编程。来自于通信顺序进程理论(Theory of Communicating Sequential Processes, CSP)。
总结:
多线程比多进程的优点如下:
线程提供了轻量级的执行上下文切换(大约100条指令),而进程是重量级的上下文切换(要上千条指令)。因为一个给定进程内的所有线程共享相同的内存空间,轻量级的上下文切换只是改变了程序的执行序列和局部变量。进程切换(重量级的上下文切换)必须改变所有内存空间。
多线程的主要缺陷:
** 等待共享资源的时候性能降低
** 需要处理线程的额外CPU花费
** 糟糕的程序设计导致不必要的复杂度
** 有可能产生一些病态行为,如饿死、竞争、死锁和活锁。
** 不同平台导致的不一致性。
思维导图:http://naotu.baidu.com/file/9011222b4dcbdb3027793be4238de678