4终结任务
1、装饰性花园
2、在阻塞时终结
四种状态:
新建(new):当线程被创建时,他只会短暂的出于这种状态。
就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。
阻塞(Blocked):线程能够运行,但有某个条件阻止他的运行。
死亡(Dead):处于死亡活终止状态的线程将不再是可调度的,并且再也不会得到CPU时间,他的任务已经结束,或不再是可运行的。
进入阻塞状态:
一个任务进入阻塞状态可能有如下原因:
通过调用sleep(milliseconds)使任务进入休眠状态,在这种情况下,任务在指定的时间内不会运行。
你通过调用wait()使线程挂起。知道线程得到notify()或notifyAll()消息(或者在Java SE5中java.util.concurrent类库中等价的signal()或signalAll()消息),线程才会进入就绪状态。
任务在等待某个输入/输出完成。
任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获得了这个锁。
3、中断
Thread.interrupt()或者新类库里面通过Executor的submit()来获得Future<?>返回值,这个Future提供cancel()以停止这个线程。
cancel:
boolean cancel(boolean mayInterruptIfRunning)试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。
参数:
mayInterruptIfRunning - 如果应该中断执行此任务的线程,则为 true;否则允许正在运行的任务运行完成。
返回:
如果无法取消任务,则返回 false,这通常是由于它已经正常完成;否则返回 true。
被互斥所阻塞
如果你尝试着在一个对象上调用其synchronized方法,而这个对象的锁已经被其他任务获得,那么调用任务讲被挂起(阻塞),直至这个锁可获得。
4、检查中断
注意,当你在线程上调用interrupt()时,中断发生的唯一时刻是在任务要进入到阻塞操作中,或者已经在阻塞操作内部时。
如果你调用interrupt()以停止某个任务,那么在run()循环碰巧没有产生任何阻塞调用的情况下,你的任务将需要第二种方式退出。
还可以通过interrupted()来检测中断状态。
5线程之间的协作
1、wait()与notifyAll()
wait()使你可以等待某个条件发生变化,而改变这个条件超出了当前方法的控制能力。只有在notify()或notifyAll()发生时,这个任务才会被唤醒并且去检查所发生的变化。因此,wait()提供了一种在任务之间对活动同步的方式。
调用sleep()的时候锁并没有释放,调用yield()也属于这种情况。wait()将释放锁。
wait还可以指定时间参数。
Demo:
错失信号:
解决方案是防止在变量上产生竞争条件。
2、notify()与notifyAll()
使用notify不是notifyAll的一种优化。
当notifyAll因某个特定的锁而被调用时,只要等待这个锁的任务才会被唤醒。
Demo:
3、生产者和消费者
Demo:
使用显示的Lock和Condition对象:
Demo:
4、生产者-消费者与队列
使用同步队列来解决任务协作问题。同步队列在任务时刻都只允许一个任务插入或移除元素。
Demo:
当你存在多个任务之间的协调时,你还可以同时创建多个BlockingQueue,用于每个步骤的协调。
5、任务间使用管道进行输入/输出
Demo:
6死锁
某个任务在等待另一个任务,而后者又等待别的任务,这样一直下去,直到这个链条上的任务又在等待第一个任务释放锁,这样就造成了一个互相等待的循环。变成了死锁。
以下四个条件同时满足时,就会发生死锁:
1、互斥条件。
2、至少有一个任务他必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。
3、资源不能被任务抢占,任务必须把资源释放当做普通事件。
4、必须有循环等待,这样,一个任务等待其他任务所持有的资源。
7新类库中的构件
1、CountDownLatch
他被也拿过来同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成。
你可以向CountDownLatch对象设置一个初始计数值,任务在这个对象上调用wait()的方法都将阻塞,直至这个计数值到达0。
其他任务在结束起工作时,可以在改对象上调用countDown()来减小这个计数值。
CountDownLatch被设计为止触发一次,计数值不能被重置。如果你需要能够重置计数值的版本,则可以使用CyclicBarrier。
Demo:
package com.partner4java.tij.countdownlatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TaskPortion implements Runnable {
private static int count = 0;
private static final int id = count++;
private CountDownLatch countDownLatch;
public TaskPortion(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
dowork();
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void dowork() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(this + " is completed");
}
@Override
public String toString() {
return "TaskPortion id:" + id;
}
}
package com.partner4java.tij.countdownlatch;
import java.util.concurrent.CountDownLatch;
public class WaitingTask implements Runnable {
private static int count = 0;
private static final int id = count++;
private CountDownLatch countDownLatch;
public WaitingTask(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
countDownLatch.await();
System.out.println("CountDownLatch barrier passed for " + this);
} catch (InterruptedException e) {
System.out.println(this + " interrupted");
}
}
@Override
public String toString() {
return "WaitingTask " + id;
}
}
package com.partner4java.tij.countdownlatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo {
private static final int SIZE = 100;
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
for(int i=0;i<10;i++){
executorService.execute(new WaitingTask(countDownLatch));
}
for(int i=0;i<SIZE;i++){
executorService.execute(new TaskPortion(countDownLatch));
}
System.out.println("Launched all tasks");
executorService.shutdown();
}
}
类库的线程安全:
2、CyclicBarrier
CountDownLatch是只触发一次的事件,而CyclicBarrier可以多次重用。
一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。
Demo:
3、DelayQueue
这是一个无界的BlockingQueue,有您关于防止实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即对头对象的延迟到期时间最长。如果没有任何延迟到期,那么就不会有任何头元素,并且poll()将返回null。
public class DelayQueue<E extends Delayed>extends AbstractQueue<E>implements BlockingQueue<E>Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满。此队列不允许使用 null 元素。
此类及其迭代器实现了 Collection 和 Iterator 接口的所有可选 方法。
此类是 Java Collections Framework 的成员。
4、PriorityBlockingQueue
这是一个很基础的优先级队列,他具有可阻塞的读取操作。
public class PriorityBlockingQueue<E>extends AbstractQueue<E>implements BlockingQueue<E>, Serializable一个无界的阻塞队列,它使用与类 PriorityQueue 相同的顺序规则,并且提供了阻塞检索的操作。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会失败(导致 OutOfMemoryError)。此类不允许使用 null 元素。依赖自然顺序的优先级队列也不允许插入不可比较的对象(因为这样做会抛出 ClassCastException)。
此类及其迭代器可以实现 Collection 和 Iterator 接口的所有可选 方法。iterator() 方法中所提供的迭代器并不 保证以特定的顺序遍历 PriorityBlockingQueue 的元素。如果需要有序地遍历,则应考虑使用 Arrays.sort(pq.toArray())。
此类是 Java Collections Framework 的成员。
5、使用ScheduledExecutor的温室控制器
public interface ScheduledExecutorServiceextends ExecutorService一个 ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
schedule 方法使用各种延迟创建任务,并返回一个可用于取消或检查执行的任务对象。scheduleAtFixedRate 和 scheduleWithFixedDelay 方法创建并执行某些在取消前一直定期运行的任务。
用 Executor.execute(java.lang.Runnable) 和 ExecutorService 的 submit 方法所提交的命令,通过所请求的 0 延迟进行安排。schedule 方法中允许出现 0 和负数延迟(但不是周期),并将这些视为一种立即执行的请求。
所有的 schedule 方法都接受相对 延迟和周期作为参数,而不是绝对的时间或日期。将以 Date 所表示的绝对时间转换成要求的形式很容易。例如,要安排在某个以后的日期运行,可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)。但是要注意,由于网络时间同步协议、时钟漂移或其他因素的存在,因此相对延迟的期满日期不必与启用任务的当前 Date 相符。 Executors 类为此包中所提供的 ScheduledExecutorService 实现提供了便捷的工厂方法。
用法示例
以下是一个带方法的类,它设置了 ScheduledExecutorService ,在 1 小时内每 10 秒钟蜂鸣一次:
6、Semaphore
正常的锁(来自concurrent.locks或内建的synchronized锁)在任何时刻都只允许一个任务访问一项资源,而计数信号量允许N个任务同时访问这个资源。你还可以将信号量看做是向外分发使用资源的的“许可证”,尽管实际上没有任何许可证对象。
public class Semaphoreextends Objectimplements Serializable一个计数信号量。从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问:
获得一项前,每个线程必须从信号量获取许可,从而保证可以使用该项。该线程结束后,将项返回到池中并将许可返回到该信号量,从而允许其他线程获取该项。注意,调用 acquire() 时无法保持同步锁定,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。
将信号量初始化为 1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。这通常也称为二进制信号量,因为它只能有两种状态:一个可用的许可,或零个可用的许可。按此方式使用时,二进制信号量具有某种属性(与很多 Lock 实现不同),即可以由线程释放“锁定”,而不是由所有者(因为信号量没有所有权的概念)。在某些专门的上下文(如死锁恢复)中这会很有用。
此类的构造方法可选地接受一个公平 参数。当设置为 false 时,此类不对线程获取许可的顺序做任何保证。特别地,闯入 是允许的,也就是说可以在已经等待的线程前为调用 acquire() 的线程分配一个许可,从逻辑上说,就是新线程将自己置于等待线程队列的头部。当公平设置为 true 时,信号量保证对于任何调用 acquire 方法的线程而言,都按照处理它们调用这些方法的顺序(即先进先出;FIFO)来选择线程、获得许可。注意,FIFO 排序必然应用到这些方法内的指定内部执行点。所以,可能某个线程先于另一个线程调用了 acquire,但是却在该线程之后到达排序点,并且从方法返回时也类似。还要注意,非同步的 tryAcquire 方法不使用公平设置,而是使用任意可用的许可。
通常,应该将用于控制资源访问的信号量初始化为公平的,以确保所有线程都可访问资源。为其他的种类的同步控制使用信号量时,非公平排序的吞吐量优势通常要比公平考虑更为重要。
此类还提供便捷的方法来同时 acquire 和 release 多个许可。小心,在未将公平设置为 true 时使用这些方法会增加不确定延期的风险。
7、Exchanger
Exchanger是两个对象之间交换对象的栅栏。当这些任务进入栅栏时,他们各自拥有一个对象,当他们离开时,他们都拥有之前由对象持有的对象。
public class Exchanger<V>extends Object两个线程可以交换对象的同步点。每个线程都在进入 exchange 方法时给出某个对象,并接受其他线程返回时给出的对象。
用法示例:以下是重点介绍的一个类,该类使用 Exchanger 在线程间交换缓冲区,因此,在需要时,填充缓冲区的线程获得一个新腾空的缓冲区,并将填满的缓冲区传递给腾空缓冲区的线程。
1、装饰性花园
2、在阻塞时终结
四种状态:
新建(new):当线程被创建时,他只会短暂的出于这种状态。
就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。
阻塞(Blocked):线程能够运行,但有某个条件阻止他的运行。
死亡(Dead):处于死亡活终止状态的线程将不再是可调度的,并且再也不会得到CPU时间,他的任务已经结束,或不再是可运行的。
进入阻塞状态:
一个任务进入阻塞状态可能有如下原因:
通过调用sleep(milliseconds)使任务进入休眠状态,在这种情况下,任务在指定的时间内不会运行。
你通过调用wait()使线程挂起。知道线程得到notify()或notifyAll()消息(或者在Java SE5中java.util.concurrent类库中等价的signal()或signalAll()消息),线程才会进入就绪状态。
任务在等待某个输入/输出完成。
任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获得了这个锁。
3、中断
Thread.interrupt()或者新类库里面通过Executor的submit()来获得Future<?>返回值,这个Future提供cancel()以停止这个线程。
cancel:
boolean cancel(boolean mayInterruptIfRunning)试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。
参数:
mayInterruptIfRunning - 如果应该中断执行此任务的线程,则为 true;否则允许正在运行的任务运行完成。
返回:
如果无法取消任务,则返回 false,这通常是由于它已经正常完成;否则返回 true。
被互斥所阻塞
如果你尝试着在一个对象上调用其synchronized方法,而这个对象的锁已经被其他任务获得,那么调用任务讲被挂起(阻塞),直至这个锁可获得。
4、检查中断
注意,当你在线程上调用interrupt()时,中断发生的唯一时刻是在任务要进入到阻塞操作中,或者已经在阻塞操作内部时。
如果你调用interrupt()以停止某个任务,那么在run()循环碰巧没有产生任何阻塞调用的情况下,你的任务将需要第二种方式退出。
还可以通过interrupted()来检测中断状态。
5线程之间的协作
1、wait()与notifyAll()
wait()使你可以等待某个条件发生变化,而改变这个条件超出了当前方法的控制能力。只有在notify()或notifyAll()发生时,这个任务才会被唤醒并且去检查所发生的变化。因此,wait()提供了一种在任务之间对活动同步的方式。
调用sleep()的时候锁并没有释放,调用yield()也属于这种情况。wait()将释放锁。
wait还可以指定时间参数。
Demo:
- package com.partner4java.tij.cooperate;
- public class Car {
- private boolean waxOn = false;
- public synchronized void waxed(){
- waxOn = true; //Ready to buff
- notifyAll();
- }
- public synchronized void buffed(){
- waxOn = false; //Ready for another coat of wax
- notifyAll();
- }
- public synchronized void waitForWaxing() throws InterruptedException{
- while(waxOn == false)
- wait();
- }
- public synchronized void waitForBuffing() throws InterruptedException{
- while(waxOn == true)
- wait();
- }
- }
- package com.partner4java.tij.cooperate;
- import java.util.concurrent.TimeUnit;
- public class WaxOff implements Runnable {
- private Car car;
- public WaxOff(Car c) {
- car = c;
- }
- @Override
- 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");
- }
- }
- package com.partner4java.tij.cooperate;
- import java.util.concurrent.TimeUnit;
- public class WaxOn implements Runnable {
- private Car car;
- public WaxOn(Car c) {
- car = c;
- }
- @Override
- public void run() {
- try {
- while(!Thread.interrupted()){
- System.out.println("Wax On!");
- TimeUnit.MILLISECONDS.sleep(200);
- car.waxed();
- car.waitForBuffing();
- }
- } catch (InterruptedException e) {
- System.out.println("Exiting via interrupt");
- }
- System.out.println("Ending Wax on Task");
- }
- }
- package com.partner4java.tij.cooperate;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.ThreadFactory;
- import java.util.concurrent.TimeUnit;
- public class WaxOMatic {
- public static void main(String[] args) throws InterruptedException {
- ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
- @Override
- public Thread newThread(Runnable r) {
- Thread thread = new Thread(r);
- thread.setDaemon(true);
- return thread;
- }
- });
- Car car = new Car();
- executorService.execute(new WaxOff(car ));
- executorService.execute(new WaxOn(car));
- TimeUnit.SECONDS.sleep(5);
- executorService.shutdown();
- }
- }
错失信号:
解决方案是防止在变量上产生竞争条件。
- T1:
- synchronized(sharedMonitor){
- <setup condition for T2>
- sharedMonitor.notify();
- }
- T2:
- synchronized(sharedMonitor){
- while(someCondition)
- sharedMonitor.wait();
- }
2、notify()与notifyAll()
使用notify不是notifyAll的一种优化。
当notifyAll因某个特定的锁而被调用时,只要等待这个锁的任务才会被唤醒。
Demo:
- package com.partner4java.tij.cooperate;
- public class Blocker {
- synchronized void waitCall(){
- try {
- while(!Thread.interrupted()){
- wait();
- System.out.println(Thread.currentThread() + " ");
- }
- } catch (Exception e) {
- }
- }
- synchronized void prod(){
- notify();
- }
- synchronized void prodAll(){
- notifyAll();
- }
- }
- package com.partner4java.tij.cooperate;
- public class Task implements Runnable {
- static Blocker blocker = new Blocker();
- @Override
- public void run() {
- blocker.waitCall();
- }
- }
- package com.partner4java.tij.cooperate;
- public class Task2 implements Runnable {
- static Blocker blocker = new Blocker();
- @Override
- public void run() {
- blocker.waitCall();
- }
- }
- package com.partner4java.tij.cooperate;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.TimeUnit;
- public class NotifyVsNotifyAll {
- public static void main(String[] args) throws InterruptedException {
- ExecutorService executorService = Executors.newCachedThreadPool();
- for(int i=0;i<5;i++){
- executorService.execute(new Task());
- }
- executorService.execute(new Task2());
- executorService.execute(new Runnable() {
- @Override
- public void run() {
- System.out.println("Task.blocker.prod");
- Task.blocker.prod();
- System.out.println("Task2.blocker.prodAll");
- Task2.blocker.prodAll();
- }
- });
- TimeUnit.MILLISECONDS.sleep(100);
- executorService.shutdownNow();
- }
- }
3、生产者和消费者
Demo:
- package com.partner4java.tij.cooperate;
- public class Meal {
- private final int orderNum;
- public Meal(int orderNum) {
- this.orderNum = orderNum;
- }
- @Override
- public String toString() {
- return "Meal [orderNum=" + orderNum + "]";
- }
- }
- package com.partner4java.tij.cooperate;
- public class WaitPerson implements Runnable {
- private Restaurant restaurant;
- public WaitPerson(Restaurant restaurant) {
- this.restaurant = restaurant;
- }
- @Override
- public void run() {
- try {
- while(!Thread.interrupted()){
- synchronized (this) {
- while(restaurant.meal == null){
- wait();
- }
- }
- 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");
- }
- }
- }
- package com.partner4java.tij.cooperate;
- import java.util.concurrent.TimeUnit;
- public class Chef implements Runnable {
- private Restaurant restaurant;
- private int count = 0;
- public Chef(Restaurant restaurant) {
- this.restaurant = restaurant;
- }
- @Override
- public void run() {
- try {
- while(!Thread.interrupted()){
- synchronized (this) {
- while(restaurant.meal != null){
- wait();
- }
- }
- 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");
- }
- }
- }
- package com.partner4java.tij.cooperate;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- 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对象:
Demo:
- class BoundedBuffer {
- final Lock lock = new ReentrantLock();
- final Condition notFull = lock.newCondition();
- final Condition notEmpty = lock.newCondition();
- final Object[] items = new Object[100];
- int putptr, takeptr, count;
- public void put(Object x) throws InterruptedException {
- lock.lock();
- try {
- while (count == items.length)
- notFull.await();
- items[putptr] = x;
- if (++putptr == items.length) putptr = 0;
- ++count;
- notEmpty.signal();
- } finally {
- lock.unlock();
- }
- }
- public Object take() throws InterruptedException {
- lock.lock();
- try {
- while (count == 0)
- notEmpty.await();
- Object x = items[takeptr];
- if (++takeptr == items.length) takeptr = 0;
- --count;
- notFull.signal();
- return x;
- } finally {
- lock.unlock();
- }
- }
- }
4、生产者-消费者与队列
使用同步队列来解决任务协作问题。同步队列在任务时刻都只允许一个任务插入或移除元素。
Demo:
- package com.partner4java.tij.blockingqueue;
- import java.util.concurrent.BlockingQueue;
- import com.partner4java.tij.util.LiftOff;
- public class LiftOffRunner implements Runnable {
- private BlockingQueue<LiftOff> rockets;
- public LiftOffRunner(BlockingQueue<LiftOff> rockets) {
- this.rockets = rockets;
- }
- public void add(LiftOff liftOff){
- try {
- rockets.add(liftOff);
- } catch (Exception e) {
- System.out.println("Interrupted during put()");
- }
- }
- @Override
- public void run() {
- try {
- while(!Thread.interrupted()){
- LiftOff liftOff = rockets.take();
- liftOff.run();
- }
- } catch (InterruptedException e) {
- System.out.println("Waking from take()");
- }
- System.out.println("Exiting LiftOffRunner");
- }
- }
- package com.partner4java.tij.blockingqueue;
- import java.util.concurrent.ArrayBlockingQueue;
- import java.util.concurrent.BlockingQueue;
- import java.util.concurrent.LinkedBlockingDeque;
- import java.util.concurrent.SynchronousQueue;
- import java.util.concurrent.TimeUnit;
- import com.partner4java.tij.util.LiftOff;
- public class TestBlockingQueues {
- public static void main(String[] args) throws InterruptedException {
- test("LinkedBlockingQueue", new LinkedBlockingDeque<LiftOff>());
- test("ArrayBlockingQueue", new ArrayBlockingQueue<LiftOff>(3));
- test("SynchronousQueue", new SynchronousQueue<LiftOff>());
- }
- private static void test(String message,BlockingQueue<LiftOff> rockets) throws InterruptedException {
- System.out.println("test:" + message);
- LiftOffRunner liftOffRunner = new LiftOffRunner(rockets);
- Thread thread = new Thread(liftOffRunner);
- thread.start();
- for(int i=0;i<5;i++){
- liftOffRunner.add(new LiftOff(6));
- }
- thread.interrupt();
- }
- }
- //支持两个附加操作的 Queue,这两个操作是:检索元素时等待队列变为非空,以及存储元素时等待空间变得可用。
- //
- //BlockingQueue 不接受 null 元素。试图 add、put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示 poll 操作失败的警戒值。
- //
- //BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 额外的元素。没有任何内部容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。
- //
- //BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持 Collection 接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不 会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。
- //
- //BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁定或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了 c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。
- //
- //BlockingQueue 实质上不 支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的 end-of-stream 或 poison 对象,并根据使用者获取这些对象的时间来对它们进行解释。
- //
- //以下是基于典型的生产者-使用者场景的一个用例。注意,BlockingQueue 可以安全地与多个生产者和多个使用者一起使用。
- //
- // class Producer implements Runnable {
- // private final BlockingQueue queue;
- // Producer(BlockingQueue q) { queue = q; }
- // public void run() {
- // try {
- // while(true) { queue.put(produce()); }
- // } catch (InterruptedException ex) { ... handle ...}
- // }
- // Object produce() { ... }
- // }
- //
- // class Consumer implements Runnable {
- // private final BlockingQueue queue;
- // Consumer(BlockingQueue q) { queue = q; }
- // public void run() {
- // try {
- // while(true) { consume(queue.take()); }
- // } catch (InterruptedException ex) { ... handle ...}
- // }
- // void consume(Object x) { ... }
- // }
- //
- // class Setup {
- // void main() {
- // BlockingQueue q = new SomeQueueImplementation();
- // Producer p = new Producer(q);
- // Consumer c1 = new Consumer(q);
- // Consumer c2 = new Consumer(q);
- // new Thread(p).start();
- // new Thread(c1).start();
- // new Thread(c2).start();
- // }
- // }
当你存在多个任务之间的协调时,你还可以同时创建多个BlockingQueue,用于每个步骤的协调。
5、任务间使用管道进行输入/输出
Demo:
- package com.partner4java.tij.piped;
- import java.io.IOException;
- import java.io.PipedWriter;
- import java.util.Random;
- import java.util.concurrent.TimeUnit;
- public class Sender implements Runnable {
- private Random random = new Random(47);
- private PipedWriter out = new PipedWriter();
- public PipedWriter getPipedWriter(){
- return out;
- }
- @Override
- public void run() {
- try {
- while(true){
- for(char c = 'A';c<='z';c++){
- out.write(c);
- TimeUnit.MILLISECONDS.sleep(random.nextInt(500));
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- package com.partner4java.tij.piped;
- import java.io.IOException;
- import java.io.PipedReader;
- public class Receiver implements Runnable {
- private PipedReader in;
- public Receiver(Sender sender) throws IOException {
- in = new PipedReader(sender.getPipedWriter());
- }
- @Override
- public void run() {
- try {
- while(true){
- System.out.println("Read:" + (char)in.read()+",");
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- package com.partner4java.tij.piped;
- import java.io.IOException;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.TimeUnit;
- public class PipedIO {
- public static void main(String[] args) throws IOException, InterruptedException {
- Sender sender = new Sender();
- Receiver receiver = new Receiver(sender);
- ExecutorService executorService = Executors.newCachedThreadPool();
- executorService.execute(sender);
- executorService.execute(receiver);
- TimeUnit.SECONDS.sleep(10);
- executorService.shutdownNow();
- }
- }
6死锁
某个任务在等待另一个任务,而后者又等待别的任务,这样一直下去,直到这个链条上的任务又在等待第一个任务释放锁,这样就造成了一个互相等待的循环。变成了死锁。
以下四个条件同时满足时,就会发生死锁:
1、互斥条件。
2、至少有一个任务他必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。
3、资源不能被任务抢占,任务必须把资源释放当做普通事件。
4、必须有循环等待,这样,一个任务等待其他任务所持有的资源。
7新类库中的构件
1、CountDownLatch
他被也拿过来同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成。
你可以向CountDownLatch对象设置一个初始计数值,任务在这个对象上调用wait()的方法都将阻塞,直至这个计数值到达0。
其他任务在结束起工作时,可以在改对象上调用countDown()来减小这个计数值。
CountDownLatch被设计为止触发一次,计数值不能被重置。如果你需要能够重置计数值的版本,则可以使用CyclicBarrier。
Demo:
package com.partner4java.tij.countdownlatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TaskPortion implements Runnable {
private static int count = 0;
private static final int id = count++;
private CountDownLatch countDownLatch;
public TaskPortion(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
dowork();
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void dowork() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(this + " is completed");
}
@Override
public String toString() {
return "TaskPortion id:" + id;
}
}
package com.partner4java.tij.countdownlatch;
import java.util.concurrent.CountDownLatch;
public class WaitingTask implements Runnable {
private static int count = 0;
private static final int id = count++;
private CountDownLatch countDownLatch;
public WaitingTask(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
countDownLatch.await();
System.out.println("CountDownLatch barrier passed for " + this);
} catch (InterruptedException e) {
System.out.println(this + " interrupted");
}
}
@Override
public String toString() {
return "WaitingTask " + id;
}
}
package com.partner4java.tij.countdownlatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo {
private static final int SIZE = 100;
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
for(int i=0;i<10;i++){
executorService.execute(new WaitingTask(countDownLatch));
}
for(int i=0;i<SIZE;i++){
executorService.execute(new TaskPortion(countDownLatch));
}
System.out.println("Launched all tasks");
executorService.shutdown();
}
}
类库的线程安全:
2、CyclicBarrier
CountDownLatch是只触发一次的事件,而CyclicBarrier可以多次重用。
一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。
Demo:
- package com.partner4java.tij.se5.cyclicbarrier;
- import java.util.Random;
- import java.util.concurrent.BrokenBarrierException;
- import java.util.concurrent.CyclicBarrier;
- public class Horse implements Runnable {
- private static int counter = 0;
- private static final int id = counter++;
- private int strides = 0;
- private static Random random = new Random(47);
- private static CyclicBarrier barrier;
- public Horse(CyclicBarrier barrier) {
- this.barrier = barrier;
- }
- public synchronized int getStrides() {
- return strides;
- }
- @Override
- public void run() {
- try {
- while(!Thread.interrupted()){
- synchronized (this) {
- strides += random.nextInt(3);
- }
- barrier.await();
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (BrokenBarrierException e) {
- e.printStackTrace();
- }
- }
- @Override
- public String toString() {
- return "Horse: " + id;
- }
- public String tracks() {
- StringBuilder builder = new StringBuilder();
- for(int i=0;i<getStrides();i++){
- builder.append("*");
- }
- builder.append(id);
- return builder.toString();
- }
- }
- package com.partner4java.tij.se5.cyclicbarrier;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.concurrent.CyclicBarrier;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.TimeUnit;
- public class HorseRace {
- static final int FINISH_LINE = 75;
- private List<Horse> horses = new ArrayList<Horse>();
- private ExecutorService executorService = Executors.newCachedThreadPool();
- private CyclicBarrier barrier;
- public HorseRace(int nHorses, final int pause) {
- barrier = new CyclicBarrier(nHorses, new Runnable() {
- @Override
- public void run() {
- StringBuilder builder = new StringBuilder();
- for(int i=0;i<FINISH_LINE;i++){
- builder.append("=");
- }
- System.out.println(builder);
- for(Horse horse:horses){
- System.out.println(horse.tracks());
- }
- for(Horse horse:horses){
- if(horse.getStrides() >= FINISH_LINE){
- System.out.println(horse + " won!");
- executorService.shutdownNow();
- return;
- }
- }
- try {
- TimeUnit.MILLISECONDS.sleep(pause);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- for(int i=0;i<nHorses;i++){
- Horse horse = new Horse(barrier);
- horses.add(horse);
- executorService.execute(horse);
- }
- }
- public static void main(String[] args) {
- int nHorses = 7;
- int pause = 200;
- new HorseRace(nHorses, pause);
- }
- }
- //一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
- //
- //CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。
- //
- //示例用法:下面是一个在并行分解设计中使用 barrier 的例子:
- //
- // class Solver {
- // final int N;
- // final float[][] data;
- // final CyclicBarrier barrier;
- //
- // class Worker implements Runnable {
- // int myRow;
- // Worker(int row) { myRow = row; }
- // public void run() {
- // while (!done()) {
- // processRow(myRow);
- //
- // try {
- // barrier.await();
- // } catch (InterruptedException ex) {
- //return;
- // } catch (BrokenBarrierException ex) {
- //return;
- // }
- // }
- // }
- // }
- //
- // public Solver(float[][] matrix) {
- // data = matrix;
- // N = matrix.length;
- // barrier = new CyclicBarrier(N,
- // new Runnable() {
- // public void run() {
- // mergeRows(...);
- // }
- // });
- // for (int i = 0; i < N; ++i)
- // new Thread(new Worker(i)).start();
- //
- // waitUntilDone();
- // }
- // }
- // 在这个例子中,每个 worker 线程处理矩阵的一行,在处理完所有的行之前,该线程将一直在屏障处等待。处理完所有的行之后,将执行所提供的 Runnable 屏障操作,并合并这些行。如果合并者确定已经找到了一个解决方案,那么 done() 将返回 true,所有的 worker 线程都将终止。
- //如果屏障操作在执行时不依赖于正挂起的线程,则线程组中的任何线程在获得释放时都能执行该操作。为方便此操作,每次调用 await() 都将返回能到达屏障处的线程的索引。然后,您可以选择哪个线程应该执行屏障操作,例如:
- //
- // if (barrier.await() == 0) {
- // // log the completion of this iteration
- // }对于失败的同步尝试,CyclicBarrier 使用了一种快速失败的、要么全部要么全不 (all-or-none) 的破坏模式:如果因为中断、失败或者超时等原因,导致线程过早地离开了屏障点,那么其他所有线程(甚至是那些尚未从以前的 await() 中恢复的线程)也将通过 BrokenBarrierException(如果它们几乎同时被中断,则用 InterruptedException)以反常的方式离开。
3、DelayQueue
这是一个无界的BlockingQueue,有您关于防止实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即对头对象的延迟到期时间最长。如果没有任何延迟到期,那么就不会有任何头元素,并且poll()将返回null。
public class DelayQueue<E extends Delayed>extends AbstractQueue<E>implements BlockingQueue<E>Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满。此队列不允许使用 null 元素。
此类及其迭代器实现了 Collection 和 Iterator 接口的所有可选 方法。
此类是 Java Collections Framework 的成员。
4、PriorityBlockingQueue
这是一个很基础的优先级队列,他具有可阻塞的读取操作。
public class PriorityBlockingQueue<E>extends AbstractQueue<E>implements BlockingQueue<E>, Serializable一个无界的阻塞队列,它使用与类 PriorityQueue 相同的顺序规则,并且提供了阻塞检索的操作。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会失败(导致 OutOfMemoryError)。此类不允许使用 null 元素。依赖自然顺序的优先级队列也不允许插入不可比较的对象(因为这样做会抛出 ClassCastException)。
此类及其迭代器可以实现 Collection 和 Iterator 接口的所有可选 方法。iterator() 方法中所提供的迭代器并不 保证以特定的顺序遍历 PriorityBlockingQueue 的元素。如果需要有序地遍历,则应考虑使用 Arrays.sort(pq.toArray())。
此类是 Java Collections Framework 的成员。
5、使用ScheduledExecutor的温室控制器
public interface ScheduledExecutorServiceextends ExecutorService一个 ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
schedule 方法使用各种延迟创建任务,并返回一个可用于取消或检查执行的任务对象。scheduleAtFixedRate 和 scheduleWithFixedDelay 方法创建并执行某些在取消前一直定期运行的任务。
用 Executor.execute(java.lang.Runnable) 和 ExecutorService 的 submit 方法所提交的命令,通过所请求的 0 延迟进行安排。schedule 方法中允许出现 0 和负数延迟(但不是周期),并将这些视为一种立即执行的请求。
所有的 schedule 方法都接受相对 延迟和周期作为参数,而不是绝对的时间或日期。将以 Date 所表示的绝对时间转换成要求的形式很容易。例如,要安排在某个以后的日期运行,可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)。但是要注意,由于网络时间同步协议、时钟漂移或其他因素的存在,因此相对延迟的期满日期不必与启用任务的当前 Date 相符。 Executors 类为此包中所提供的 ScheduledExecutorService 实现提供了便捷的工厂方法。
用法示例
以下是一个带方法的类,它设置了 ScheduledExecutorService ,在 1 小时内每 10 秒钟蜂鸣一次:
- import static java.util.concurrent.TimeUnit.*;
- class BeeperControl {
- private final ScheduledExecutorService scheduler =
- Executors.newScheduledThreadPool(1);
- public void beepForAnHour() {
- final Runnable beeper = new Runnable() {
- public void run() { System.out.println("beep"); }
- };
- final ScheduledFuture<?> beeperHandle =
- scheduler.scheduleAtFixedRate(beeper, 10, 10, SECONDS);
- scheduler.schedule(new Runnable() {
- public void run() { beeperHandle.cancel(true); }
- }, 60 * 60, SECONDS);
- }
- }
6、Semaphore
正常的锁(来自concurrent.locks或内建的synchronized锁)在任何时刻都只允许一个任务访问一项资源,而计数信号量允许N个任务同时访问这个资源。你还可以将信号量看做是向外分发使用资源的的“许可证”,尽管实际上没有任何许可证对象。
public class Semaphoreextends Objectimplements Serializable一个计数信号量。从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问:
- class Pool {
- private static final MAX_AVAILABLE = 100;
- private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
- public Object getItem() throws InterruptedException {
- available.acquire();
- return getNextAvailableItem();
- }
- public void putItem(Object x) {
- if (markAsUnused(x))
- available.release();
- }
- // Not a particularly efficient data structure; just for demo
- protected Object[] items = ... whatever kinds of items being managed
- protected boolean[] used = new boolean[MAX_AVAILABLE];
- protected synchronized Object getNextAvailableItem() {
- for (int i = 0; i < MAX_AVAILABLE; ++i) {
- if (!used[i]) {
- used[i] = true;
- return items[i];
- }
- }
- return null; // not reached
- }
- protected synchronized boolean markAsUnused(Object item) {
- for (int i = 0; i < MAX_AVAILABLE; ++i) {
- if (item == items[i]) {
- if (used[i]) {
- used[i] = false;
- return true;
- } else
- return false;
- }
- }
- return false;
- }
- }
获得一项前,每个线程必须从信号量获取许可,从而保证可以使用该项。该线程结束后,将项返回到池中并将许可返回到该信号量,从而允许其他线程获取该项。注意,调用 acquire() 时无法保持同步锁定,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。
将信号量初始化为 1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。这通常也称为二进制信号量,因为它只能有两种状态:一个可用的许可,或零个可用的许可。按此方式使用时,二进制信号量具有某种属性(与很多 Lock 实现不同),即可以由线程释放“锁定”,而不是由所有者(因为信号量没有所有权的概念)。在某些专门的上下文(如死锁恢复)中这会很有用。
此类的构造方法可选地接受一个公平 参数。当设置为 false 时,此类不对线程获取许可的顺序做任何保证。特别地,闯入 是允许的,也就是说可以在已经等待的线程前为调用 acquire() 的线程分配一个许可,从逻辑上说,就是新线程将自己置于等待线程队列的头部。当公平设置为 true 时,信号量保证对于任何调用 acquire 方法的线程而言,都按照处理它们调用这些方法的顺序(即先进先出;FIFO)来选择线程、获得许可。注意,FIFO 排序必然应用到这些方法内的指定内部执行点。所以,可能某个线程先于另一个线程调用了 acquire,但是却在该线程之后到达排序点,并且从方法返回时也类似。还要注意,非同步的 tryAcquire 方法不使用公平设置,而是使用任意可用的许可。
通常,应该将用于控制资源访问的信号量初始化为公平的,以确保所有线程都可访问资源。为其他的种类的同步控制使用信号量时,非公平排序的吞吐量优势通常要比公平考虑更为重要。
此类还提供便捷的方法来同时 acquire 和 release 多个许可。小心,在未将公平设置为 true 时使用这些方法会增加不确定延期的风险。
7、Exchanger
Exchanger是两个对象之间交换对象的栅栏。当这些任务进入栅栏时,他们各自拥有一个对象,当他们离开时,他们都拥有之前由对象持有的对象。
public class Exchanger<V>extends Object两个线程可以交换对象的同步点。每个线程都在进入 exchange 方法时给出某个对象,并接受其他线程返回时给出的对象。
用法示例:以下是重点介绍的一个类,该类使用 Exchanger 在线程间交换缓冲区,因此,在需要时,填充缓冲区的线程获得一个新腾空的缓冲区,并将填满的缓冲区传递给腾空缓冲区的线程。
- class FillAndEmpty {
- Exchanger<DataBuffer> exchanger = new Exchanger();
- DataBuffer initialEmptyBuffer = ... a made-up type
- DataBuffer initialFullBuffer = ...
- class FillingLoop implements Runnable {
- public void run() {
- DataBuffer currentBuffer = initialEmptyBuffer;
- try {
- while (currentBuffer != null) {
- addToBuffer(currentBuffer);
- if (currentBuffer.full())
- currentBuffer = exchanger.exchange(currentBuffer);
- }
- } catch (InterruptedException ex) { ... handle ... }
- }
- }
- class EmptyingLoop implements Runnable {
- public void run() {
- DataBuffer currentBuffer = initialFullBuffer;
- try {
- while (currentBuffer != null) {
- takeFromBuffer(currentBuffer);
- if (currentBuffer.empty())
- currentBuffer = exchanger.exchange(currentBuffer);
- }
- } catch (InterruptedException ex) { ... handle ...}
- }
- }
- void start() {
- new Thread(new FillingLoop()).start();
- new Thread(new EmptyingLoop()).start();
- }
- }