21.2 基本的线程机制
21.2.1 定义任务
线程可以驱动任务,任务由Runnable接口提供。定义任务,只需实现Runnable接口并实现run()方法。
MainThread
class LiftOff implements Runnable {
protected int countDown = 10;
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 + ")" + " ";
}
@Override
public void run() {
System.out.println("Thread:" + Thread.currentThread());
while (countDown-- > 0) {
System.out.println(status());
}
}
}
class MainThread {
public static void chapter21_2() {
System.out.println("MainThread:" + Thread.currentThread());
LiftOff lauch = new LiftOff();
lauch.run();
}
}
id用来区分任务的多个实例,它是final的,一旦被初始化之后就不能被修改。
21.2.2 Thread类
Thread构造器需要一个Runnable对象。调用Thread对象的start()方法,为该线程执行初始化操作,然后自动调用Runnable的run()方法。
BasicThreads
class BasicThreads {
public static void chapter21_2() {
System.out.println("BasicThreads:" + Thread.currentThread());
Thread t = new Thread(new LiftOff());
t.start();
}
}
21.2.3 使用Executor
java.util.concurrent包的Executor管理Thread对象,简化了并发编程。
CachedThreadPool
class CachedThreadPool {
public static void chapter21_2() {
System.out.println("CachedThreadPool:" + Thread.currentThread());
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0;i<3;i++) {
executorService.execute(new LiftOff());
}
executorService.shutdown();
}
}
CachedThreadPool将为每个任务都创建一个线程。对shutdown()的调用可防止新任务被提交给这个Executor。
FixedThreadPool使用有限的线程集来执行任务。
SingleThreadExecutor是线程数为1的FixedThreadPool。
21.2.4 从任务中产生返回值
Runnable是执行工作的独立任务,它不返回任何值。如果你希望任务在完成后能够返回值,可以实现Callable接口。
Callable是一个具有类型参数的泛型,它的类型参数表示从call()中的返回值。即Callable返回值。
CallableDemo
class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id) {
this.id = id;
}
@Override
public String call() throws Exception {
System.out.println("TaskWithResult:" + Thread.currentThread());
return "result id: " + id;
}
}
class CallableDemo {
public static void chapter21_2() {
System.out.println("CallableDemo:" + Thread.currentThread());
ExecutorService executorService = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<>();
for(int i = 0; i < 3; i++) {
results.add(executorService.submit(new TaskWithResult(i)));
}
for(Future<String> fs:results) {
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
submit()方法会产生Future对象。可以用isDone()判断Future是否已经完成。任务完成时,isDone返回,可调用get()获取该结果;也可以不用isDone判断就直接调用get(),get()将阻塞直到结果准备就绪。
21.2.9 编码的变体
目前为止,任务类都实现了Runnable。你可能会希望使用直接从Thread继承的方式:
SimpleThread
class SimpleThread extends Thread {
private int countDown = 5;
private static int threadCount = 0;
public SimpleThread() {
super(Integer.toString(++threadCount)); //store the thread name
start();
}
public String toString() {
return "#" + getName() + "(" + countDown + ")";
}
public void run() {
System.out.println("SimpleThread:" + Thread.currentThread());
while(true) {
System.out.println(this);
if(--countDown==0) {
break;
}
}
}
public static void chapter21_2() {
for(int i = 0; i < 3; i++) {
new SimpleThread();
}
}
}
问题是构造器里启动了线程,意味这另一个线程会在构造器结束之前开始执行,存在风险。
21.2.11 加入一个线程
某线程在另一个线程t上调用t.join(),此线程会阻塞,直到目标线程结束。也可以在调用join()时带上一个超时参数。
线程可以被中断,做法是在调用线程上调用interupt方法。
Joining
class Sleeper extends Thread {
private int duration;
public Sleeper(String name, int sleepTime) {
super(name);
duration = sleepTime;
start();
}
public void run() {
try {
sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(getName() + " was interrupted.");
System.out.println("isInterrupted(): " + isInterrupted());
return;
}
System.out.println(getName() + " has awakened");
}
}
class Joiner extends Thread {
private Sleeper sleeper;
public Joiner(String name, Sleeper sleeper) {
super(name);
this.sleeper = sleeper;
start();
}
public void run() {
try {
sleeper.join();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("isInterrupted(): " + isInterrupted());
}
System.out.println(getName() + " join completed");
}
}
class Joining {
public static void chapter21_2() {
Sleeper sleepy = new Sleeper("Sleepy",1500);
Sleeper grumpy = new Sleeper("Grumpy",1500);
Joiner sleepy_join = new Joiner("Sleepy_join",sleepy);
Joiner grumpy_join = new Joiner("Grumpy_join",grumpy);
grumpy.interrupt();
}
}
Joiner线程将通过Sleeper对象上调用join()方法来等待Sleeper结束。
当另一个线程在该线程上调用interrupt()时,会给该线程设定一个标志,表明该线程已经被中断。然而,异常被捕获时将清理这个标志,所以在catch中异常被捕获时这个标志总是假的。
21.2.14 捕获异常
一旦异常逃出线程任务的run()方法,它就会向外传播。
NaiveExceptionHandling
class ExceptionThread implements Runnable {
@Override
public void run() {
throw new RuntimeException();
}
}
class NativeExceptionHandling {
public static void chapter21_2() {
try {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new ExceptionThread());
} catch(RuntimeException ue) {
System.out.println("Exception has been handled!");
}
}
}
try-catch不会捕获该异常,因为是另一个线程抛出的异常。
Thread.UncaughtExceptionHandler允许在每个Thread对象上附着一个异常处理器。Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用。Executors.newCachedThreadPool()可以接收ThreadFactory参数。
CaptureUncaughtException
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("!!! caught " + e);
}
}
class HandlerThreadFactory implements ThreadFactory {
@Override
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 MyUncaughtExceptionHandler());
System.out.println("eh = " + t.getUncaughtExceptionHandler());
return t;
}
}
class CaptureUncaughtException {
public static void chapter21_2() {
ExecutorService executorService = Executors.newCachedThreadPool(new HandlerThreadFactory());
executorService.execute(new ExceptionThread());
}
}
若代码中使用相同的异常处理器,将异常处理器设置为默认的即可
SettingDefaultHandler
class SettingDefaultHandler {
public static void chapter21_2() {
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new ExceptionThread());
}
}
21.3 共享受限资源(互斥)
21.3.2 解决共享资源竞争
即synchronized方法。
所有对象默认含有单一的锁。当在对象上调用synchronized方法时,对象被加锁。对象的其他synchronized方法只有等到之前方法调用完毕才能被调用。
一个任务线程可以多次获得对象的锁(即锁可重入,以引用计数的方式)。如果一个方法在同一个对象上调用了第二个方法,后者又调用了同一个对象的另一个方法,就会发生这种情况。只有先获得了锁的线程任务才能允许继续获取多个锁。
SynchronizedEvenGenerator
abstract class IntGenerator {
public abstract int next();
}
class SynchronizedEvenGenerator extends IntGenerator {
private int currentValue = 0;
@Override
public synchronized int next() {
++currentValue;
++currentValue;
return currentValue;
}
}
同步偶数生成器
使用显式的Lock对象
MutexEvenGenerator
class MutexEvenGenerator extends IntGenerator {
private int currentValue = 0;
private Lock lock;
@Override
public int next() {
lock.lock();
try {
++currentValue;
++currentValue;
return currentValue;
} finally {
lock.unlock();
}
}
}
执行顺序:try finally return
21.3.3 原子性与可视性
原子性可应用于除了long和double之外的基本类型的"简单操作"。如简单的赋值和返回操作。(对应写操作和读操作)
(1)定义long或double变量时,如果使用volatile关键字,会获得原子性。
(2)若将域声明为volatile的,volatile关键字确保了可视性。只要对这个域产生了写操作,所有的读操作可以看到这个修改。即使使用了本地缓存,volatile域会立即被写入到主存中,而读操作就发生在主存中。
提醒,第一选择应该是使用synchronized关键字。
AtomicityTest
class AtomicityTest implements Runnable {
private int i = 0;
public int flag = 0;
public int getValue() {
//public synchronized int getValue() {
return i;
}
private synchronized void evenIncrease() {
i++;
i++;
}
@Override
public void run() {
while(true) {
evenIncrease();
if (flag == 1) {
break;
}
}
System.out.println("AtomicityTest 2");
}
public static void chapter21_3() {
ExecutorService executorService = Executors.newCachedThreadPool();
AtomicityTest atomicityTest = new AtomicityTest();
executorService.execute(atomicityTest);
while (true) {
int val = atomicityTest.getValue();
if (val % 2 != 0) {
System.out.println(val);
atomicityTest.flag = 1;
break;
}
}
System.out.println("AtomicityTest 1");
}
}
return i虽然是原子性操作,但是缺少同步使得其数值可在不稳定的中间状态时被读取。除此以外,i不是volatile的,还存在可视性问题。
修改:synchronized getValue()
21.3.5 临界区
同步控制块
synchronized(syncObject) {
//xxx
}
21.3.6 在其他对象上同步
一般使用同步控制块,synchronized(this) { xxx };如果必须在另一个对象上同步:
SynchObject
class DualSynch {
private Object syncObject = new Object();
public synchronized void f() {
for (int i=0;i<5;i++) {
System.out.println("f()");
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.yield();
}
}
public void g() {
synchronized (syncObject) {
for (int i=0;i<5;i++) {
System.out.println("g()");
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.yield();
}
}
}
}
class SynchObject {
public static void chapter21_3() {
DualSynch dualSynch = new DualSynch();
new Thread() {
public void run() {
dualSynch.f();
}
}.start();
dualSynch.g();
}
}
f()在this同步,g()在syncObject上同步,二者是相互独立的。
21.4 终结任务(中断)
21.4.1 装饰性花园
OrnamentalGarden
class Entrance implements Runnable {
private static volatile boolean canceled = false;
public static void cancel() {
canceled = true;
}
@Override
public void run() {
while(!canceled) {
try {
System.out.println("Entrance " + this + "do something!");
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class OrnamentalGarden {
public static void chapter21_4() {
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0;i<2;i++) {
executorService.execute(new Entrance());
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Entrance.cancel();
executorService.shutdown();
try {
if(!executorService.awaitTermination(50,TimeUnit.MILLISECONDS)) {
System.out.println("Some tasks were not terminated!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
shutdown只是起到通知关闭线程的作用,具体的关闭线程方法是:
在shutdown方法调用后,接着调用awaitTermination方法。这时只需要等待awaitTermination方法里第一个参数指定的时间。
ExcecutorService.awaitTermination()等待每个任务结束,如果所有任务在超时时间达到之前全部结束,则返回true,否则返回false,表示不是所有的任务都已经结束。
21.4.2 在阻塞时终结
线程状态:
(1)新建(new):分配系统资源,初始化。
(2)就绪(Runnable):这种状态下,只要调度器把时间片分给线程,线程就可以运行。
(3)阻塞(Blocked)
(4)死亡(Dead)
进入阻塞状态的原因:
(1)sleep()进入休眠状态
(2)wait()使线程挂起,直到线程得到notify()或notifyAll()消息。或java.util.concurrent的signal()或signalAll()消息。
(3)等待某个输入输出(IO)
(4)等待互斥锁
21.4.3 中断
Thread类的interrupt()方法:终止被阻塞的任务,设置线程为中断状态。若线程为阻塞状态,调用interrupt()方法会抛出InterruptedException。当抛出该异常或调用Thread.interrupted()时,中断状态将被复位。
(1)同时关闭Excecutor的所有线程:
在Excecutor上调用shutdownNow(),将发送interrupt()调用给他启动的所有线程。
(2)单独中断Excecutor的单个线程:
通过调用submit()启动任务,可持有该任务的上下文Future<?>,在其上调用cancel(),可在该线程上调用interrupt()以停止这个线程。
Interrupting
class SleepBlocked implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class IOBlocked implements Runnable {
private InputStream in;
public IOBlocked(InputStream is) {
in = is;
}
@Override
public void run() {
try {
System.out.println("in.read() >>> ");
in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class SynchronizedBlocked implements Runnable {
public SynchronizedBlocked() {
new Thread(){
public void run() {
f();
}
}.start();
}
public synchronized void f() {
while(true) {
Thread.yield();
}
}
@Override
public void run() {
System.out.println("try to call f() >>>");
f();
System.out.println("called f()");
}
}
class Interrupting {
private static ExecutorService executorService = Executors.newCachedThreadPool();
static void test(Runnable r) throws InterruptedException {
Future<?> f = executorService.submit(r);
TimeUnit.SECONDS.sleep(1);
System.out.println("Interrupting " + r.getClass().getName());
f.cancel(true);
System.out.println("Interrupt sent to " + r.getClass().getName());
}
public static void chapter21_4() {
try {
test(new SleepBlocked());
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
test(new IOBlocked(System.in));
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
test(new SynchronizedBlocked());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这个程序证明,SleepBlocked是可中断的阻塞,而IOBlocked和SynchronizedBlocked是不可中断的阻塞。
18章的nio类提供了更人性化的I/O中断,被阻塞的nio通道会自动地响应中断
NIOInterruption
class NIOBlocked implements Runnable {
private final SocketChannel sc;
NIOBlocked(SocketChannel sc) {
this.sc = sc;
}
@Override
public void run() {
System.out.println(" read in >>> " + this);
try {
sc.read(ByteBuffer.allocate(1));
} catch (ClosedByInterruptException e) {
System.out.println("catch e:" + e);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(" read out >>> " + this);
}
}
class NIOInterruption {
public static void chapter21_4() {
ExecutorService executorService = Executors.newCachedThreadPool();
try {
ServerSocket serverSocket = new ServerSocket(8080);
InetSocketAddress isa = new InetSocketAddress("localhost", 8080);
SocketChannel sc = SocketChannel.open(isa);
Future<?> f = executorService.submit(new NIOBlocked(sc));
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Interrupting ");
f.cancel(true);
System.out.println("Interrupt sented ");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Java SE5并发类库中添加了一个特性,即在ReentrantLock上阻塞的任务具备可以被中断的能力
Interrupting2
class BlockedMutex {
private Lock lock = new ReentrantLock();
public BlockedMutex() {
System.out.println("BlockedMutex() " + currentThread());
lock.lock();
}
public void f() {
System.out.println("f() " + currentThread());
try {
lock.lockInterruptibly();
System.out.println("f() acquired lock!");
} catch (InterruptedException e) {
System.out.println("f() InterruptedException!");
}
}
}
class Blocked2 implements Runnable {
BlockedMutex blockedMutex = new BlockedMutex();
@Override
public void run() {
System.out.println("Blocked2() in >>>");
blockedMutex.f();
System.out.println("Blocked2() out >>>");
}
}
class Interrupting2 {
public static void chapter21_4() {
Thread thread = new Thread(new Blocked2());
thread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Interrupting ");
thread.interrupt();
System.out.println("Interrupted ");
}
}
21.4.4 检查中断
可调用interrupted()来检查中断状态,这不仅可以告诉你interrupt()是否被调用过,而且还可以清除中断状态。
21.5 线程之间的协作(同步)
21.5.1 wait()与notifyAll()
[1]wait()会将任务线程挂起,只有notify()或notifyall()发生时,任务才会被唤醒去检查变化。
[2]当任务的方法里调用wait()时,线程被挂起,对象上的锁被释放。因为wait()将释放锁,意味着另一个任务可以获得这个锁,因此该对象的其他synchronized方法可以被调用。
[3]wait()可以带超时参数。
[4]wait(),notify()及notifyAll()都是基类Object的方法,不是Thread类的。只能在同步控制方法或同步控制块里调用wait(),notify()及nofityAll()。
WaxOMatic 打蜡与抛光
class Car {
private boolean waxOn = false;
//打蜡
public synchronized void waxed() {
waxOn = true;
notifyAll();
}
//抛光
public synchronized void buffed() {
waxOn = false;
notifyAll();
}
//等待打蜡
public synchronized void waitForWax() throws InterruptedException {
while(waxOn == false) {
wait();
}
}
//等待抛光
public synchronized void waitForBuff() throws InterruptedException {
while(waxOn == true) {
wait();
}
}
}
//打蜡,等待抛光
class WaxOn implements Runnable {
private Car car;
public WaxOn(Car c) {
car = c;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(200);
System.out.println("Wax On! 打蜡!");
car.waxed();
System.out.println("Wax On! 等待抛光!");
car.waitForBuff();
}
} catch (InterruptedException e) {
System.out.println("WaxOn InterruptedException!");
}
System.out.println("WaxOn done!");
}
}
//等待打蜡,抛光
class WaxOff implements Runnable {
private Car car;
public WaxOff(Car c) {
car = c;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
System.out.println("Wax Off! 等待打蜡!");
car.waitForWax();
TimeUnit.MILLISECONDS.sleep(200);
System.out.println("Wax Off! 抛光!");
car.buffed();
}
} catch (InterruptedException e) {
System.out.println("WasOff InterruptedException!");
}
System.out.println("WaxOff done!");
}
}
class WaxOMatic {
public static void chapter21_5() {
Car car = new Car();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new WaxOff(car));
executorService.execute(new WaxOn(car));
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdownNow();
}
}
调用ExecutorService的shutdownNow()时,会调用由他控制的所有线程的interrupt()。
21.5.3 生产者与消费者
Restaurant
class Meal {
private final int orderNum;
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;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
synchronized (this) {
while (restaurant.meal == null) {
wait(); //wait for chef to produce a meal
}
}
System.out.println("WaitPerson got " + restaurant.meal);
synchronized (restaurant.chef) {
restaurant.meal = null;
restaurant.chef.notifyAll(); //notify chef to produce another meal
}
}
} catch (InterruptedException e) {
System.out.println("WaitPerson InterruptedException");
}
System.out.println("WaitPerson out <<<");
}
}
class Chef implements Runnable {
private Restaurant restaurant;
private int count = 0;
public Chef(Restaurant r) {
restaurant = r;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
synchronized (this) {
while (restaurant.meal != null) {
wait();
}
}
if (++count == 5) {
System.out.println("Out of food! Closing!");
restaurant.executorService.shutdownNow();
}
System.out.println("Chef produce meal: " + count);
synchronized (restaurant.waitPerson) {
restaurant.meal = new Meal(count);
restaurant.waitPerson.notifyAll();
}
//注意加sleep,否则while循环退出,不产生中断
TimeUnit.MILLISECONDS.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("Chef InterruptedException");
}
System.out.println("Chef out <<<");
}
}
class Restaurant {
Meal meal;
ExecutorService executorService = Executors.newCachedThreadPool();
WaitPerson waitPerson = new WaitPerson(this);
Chef chef = new Chef(this);
Restaurant() {
executorService.execute(chef);
executorService.execute(waitPerson);
}
public static void chapter21_5() {
new Restaurant();
}
}
run()中,WaitPerson进入wait()模式,停止其任务,直到被Chef的notifyAll()唤醒。
一旦Chef送上Meal并通知WaitPerson,这个Chef就将等待,直到WaitPerson收集到订单并通知Chef,之后Chef就可以烧下一份Meal了。
shutdownNow()发出中断interrupt()后,WaitPerson是在wait()时抛出InterruptedException异常,Chef是在sleep()时抛出InterruptedException异常。
使用显式的Lock和Condition对象
可通过在Condition上调用await()来挂起一个任务。可通过signal()来唤醒一个任务或通过signalAll()来唤醒所有在这个Condition的任务。
WaxOMatic2
class Car2 {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean waxOn = false;
//打蜡
public void waxed() {
lock.lock();
try {
waxOn = true;
condition.signalAll();
} finally {
lock.unlock();
}
}
//抛光
public void buffed() {
lock.lock();
try {
waxOn = false;
condition.signalAll();
} finally {
lock.unlock();
}
}
//等待打蜡
public void waitForWax() throws InterruptedException {
lock.lock();
try {
while (waxOn == false) {
condition.await();
}
} finally {
lock.unlock();
}
}
//等待抛光
public void waitForBuff() throws InterruptedException {
lock.lock();
try {
while(waxOn == true) {
condition.await();
}
} finally {
lock.unlock();
}
}
}
//打蜡,等待抛光
class WaxOn2 implements Runnable {
private Car2 car;
public WaxOn2(Car2 c) {
car = c;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(200);
System.out.println("Wax On! 打蜡!");
car.waxed();
System.out.println("Wax On! 等待抛光!");
car.waitForBuff();
}
} catch (InterruptedException e) {
System.out.println("WaxOn InterruptedException!");
}
System.out.println("WaxOn done!");
}
}
//抛光,等待打蜡
class WaxOff2 implements Runnable {
private Car2 car;
public WaxOff2(Car2 c) {
car = c;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
System.out.println("Wax Off! 等待打蜡!");
car.waitForWax();
TimeUnit.MILLISECONDS.sleep(200);
System.out.println("Wax Off! 抛光!");
car.buffed();
}
} catch (InterruptedException e) {
System.out.println("WasOff InterruptedException!");
}
System.out.println("WaxOff done!");
}
}
class WaxOMatic2 {
public static void chapter21_5() {
Car2 car = new Car2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new WaxOff2(car));
executorService.execute(new WaxOn2(car));
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdownNow();
}
}
每个lock()的调用必须紧跟一个try-finally子句,保证所有情况下都可以释放锁。
21.5.4 生产者与消费者队列
同步队列 java.util.concurent.BlockingQueue
无界队列:LinkedBlockingQueue
有界队列:ArrayBlockingQueue
TestBlockingQueues
class LiftOffRunner implements Runnable {
private BlockingQueue<LiftOff> rockets;
public LiftOffRunner(BlockingQueue<LiftOff> queue) {
rockets = queue;
}
public void add(LiftOff liftOff) {
try {
rockets.put(liftOff);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
while(!Thread.interrupted()) {
LiftOff rocket = rockets.take();
rocket.run();
}
} catch (InterruptedException e) {
System.out.println("LiftOffRunner InterruptedException");
}
System.out.println("Exit LiftOffRunner");
}
}
class TestBlockingQueues {
static void test(String msg, BlockingQueue<LiftOff> queue) {
LiftOffRunner liftOffRunner = new LiftOffRunner(queue);
Thread thread = new Thread(liftOffRunner);
thread.start();
//3个LiftOff,countDown 5
for(int i=0;i<3;i++) {
liftOffRunner.add(new LiftOff(5));
}
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
public static void chapter21_5() {
System.out.println("test LinkedBlockingDeque =====================");
test("LinkedBlockingDeque", new LinkedBlockingDeque<>()); //Unlimited size
System.out.println("test ArrayBlockingQueue =====================");
test("ArrayBlockingQueue", new ArrayBlockingQueue<>(2)); //Fixed size
System.out.println("test SynchronousQueue =====================");
test("SynchronousQueue", new SynchronousQueue<>()); //1 size
}
}
21.5.5 任务间使用管道进行输入/输出
管道:阻塞队列
PipedWriter类:允许任务线程向管道写
PipedReader类:允许不同任务线程从同一个管道读取
PipedIO
class Sender implements Runnable {
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(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());
}
@Override
public void run() {
try {
while(true) {
System.out.println("Read: " + (char)in.read() + " ");
}
} catch (IOException e) {
System.out.println(e + " Receiver read exception");
}
}
}
class PipedIO {
public static void chapter21_5() {
Sender sender = new Sender();
try {
Receiver receiver = new Receiver(sender);
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(sender);
executorService.execute(receiver);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdownNow();
} catch (IOException e) {
e.printStackTrace();
}
}
}
两个任务线程使用一个管道进行通信的例子。Sender把数据放进Writer,然后休眠一段时间。Receiver没有sleep()和wait()。当Receiver调用read()时,如果没有更多的数据,管道将自动阻塞。shutdownNow()被调用时,可看到PipedReader与普通I/O之间最重要的差异:PipedReader是可中断的。若将in.read()修改为System.in.read(),则shutdownNow()发出的interrupt()不能中断read()调用。
21.6 死锁
2个哲学家,2根筷子,围坐在桌子周围,每人之间放一根筷子。当哲学家要就餐时,必须同时得到左边和右边的筷子,否则等待。
DeadLockingDiningPhilosophers
class Chopstick {
private boolean taken = false;
public synchronized void take() throws InterruptedException {
while(taken) {
wait();
}
taken = true;
}
public synchronized void drop() {
taken = false;
notifyAll();
}
}
class Philosopher implements Runnable {
private Chopstick left;
private Chopstick right;
private int id = 0;
private int ponderFactor = 0; //ponder沉思
public Philosopher(Chopstick left, Chopstick right, int ident, int ponder) {
this.left = left;
this.right = right;
id = ident;
ponderFactor = ponder;
}
private void pause() throws InterruptedException {
if(ponderFactor == 0)
return;
TimeUnit.MILLISECONDS.sleep(ponderFactor*100);
}
@Override
public void run() {
try {
while(!Thread.interrupted()){
System.out.println(this + " " + "thinking!");
//pause();
System.out.println(this + " " + "grabbing right!");
right.take();
System.out.println(this + " " + "grabbing left!");
left.take();
System.out.println(this + " " + "eating!");
pause();
right.drop();
left.drop();
}
} catch (InterruptedException e) {
System.out.println(this + " " + "exiting via interrupt!");
}
}
public String toString() {
return "Philosopher " + id;
}
}
class DeadlockingDiningPhilosophers {
public static void chapter21_6() throws InterruptedException {
int ponder = 3;
int size = 2;
ExecutorService executorService = Executors.newCachedThreadPool();
Chopstick[] chopsticks = new Chopstick[size];
for (int i=0; i<size; i++) {
chopsticks[i] = new Chopstick();
}
for (int i=0; i<size; i++) {
executorService.execute(new Philosopher(chopsticks[i], chopsticks[(i+1)%size], i ,ponder));
}
TimeUnit.SECONDS.sleep(5);
executorService.shutdownNow();
}
}
哲学家就餐死锁,2个人若同时先拿起右边筷子,则死锁。
死锁条件:
(1)互斥。资源不能共享,一根筷子一次只能被一个哲学家使用。
(2)至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。即要发生死锁,哲学家必须拿着一根筷子并且等待另一根筷子。
(3)资源不能被任务抢占。哲学家不能从其他哲学家里抢夺筷子。
(4)必须有循环等待。哲学家1等哲学家2的筷子,哲学家2又等哲学家1的筷子。
防止死锁最容易破坏第4个条件。有这个条件的原因是每个哲学家都试图用特定的顺序拿筷子:先右后左。
所以解决死锁方法是:最后一个哲学家先拿左边筷子,后拿右边筷子。
FixedDiningPhilosophers
class FixedDiningPhilosophers {
public static void chapter21_6() throws InterruptedException {
int ponder = 3;
int size = 2;
ExecutorService executorService = Executors.newCachedThreadPool();
Chopstick[] chopsticks = new Chopstick[size];
for (int i=0; i<size; i++) {
chopsticks[i] = new Chopstick();
}
for (int i=0; i<size; i++) {
if (i < size-1) {
executorService.execute(new Philosopher(chopsticks[i], chopsticks[i + 1], i, ponder));
} else {
executorService.execute(new Philosopher(chopsticks[0], chopsticks[i], i, ponder));
}
}
TimeUnit.SECONDS.sleep(5);
executorService.shutdownNow();
}
}
21.7 新类库中的构件
21.7.1 CountDownLatch 锁存器
可以向CountDownLatch设置一个初始计数值,任何在这个对象上调用wait()的方法都将阻塞,直到这个计数值为0。
CountDownLatch被设计为只触发一次,计数值不能被重置。如果需要重置计数值,可以使用CyclicBarrier。
调用countDown()会减少计数值,调用await()会被阻塞直到计数值为0。
CountDownLatchDemo
// Performs some portion of a task
class TaskPortion implements Runnable {
private static int counter = 0;
private final int id = counter++;
private CountDownLatch latch = null;
TaskPortion(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
doWork();
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void doWork() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(500);
System.out.println(this + " completed");
}
public String toString() {
return "" + id;
}
}
// Waits on the CountDownLatch
class WaitingTask implements Runnable {
private static int counter = 0;
private final int id = counter++;
private CountDownLatch latch = null;
WaitingTask(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
latch.await();
System.out.println("Latch barrier passed for " + this);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String toString() {
return "WaitingTask " + id;
}
}
class CountDownLatchDemo {
static final int SIZE = 10;
public static void chapter21_7() {
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatch latch = new CountDownLatch(SIZE);
for (int i=0;i<10;i++) {
executorService.execute(new WaitingTask(latch));
}
for (int i=0;i<SIZE;i++) {
executorService.execute(new TaskPortion(latch));
}
System.out.println("Launched all tasks");
executorService.shutdown();
System.out.println("exit!!!");
}
}
例子中CountDownLatch设置为10,10个WaitingTask任务阻塞在await(),10个TaskPosition任务执行后,CountDownLatch计数值才为0,WaitingTask任务才可以继续执行。
21.7.2 CyclicBarrier 循环栅栏
CountDownLatch是只触发一次的事件(计数为0),而CyclicBarrier可以多次重用。
21.7.3 DelayQueue 延迟同步队列
这是一个无界的同步队列,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。
这种队列是有序的,即队头对象的延迟到期的时间最长。
如果没有任何延迟到期,就不会有任何头元素,且poll()将返回null。
DelayQueueDemo
class DelayedTask implements Runnable, Delayed {
private static int counter = 0;
private final int id = counter++;
private int delta = 0; //ms
private long trigger = 0; //ns
protected static List<DelayedTask> sequence = new ArrayList<>();
public DelayedTask(int milliseconds) {
delta = milliseconds;
trigger = System.nanoTime() + NANOSECONDS.convert(delta, MILLISECONDS);
sequence.add(this);
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(trigger - System.nanoTime(), NANOSECONDS);
}
@Override
public int compareTo(Delayed o) {
DelayedTask that = (DelayedTask)o;
if(trigger < that.trigger) return -1;
if(trigger > that.trigger) return 1;
return 0;
}
@Override
public void run() {
System.out.println(this + " ");
}
public String toString() {
return delta + " Task " + id;
}
}
class EndSentinel extends DelayedTask { //哨兵
private ExecutorService exec;
public EndSentinel(int delay, ExecutorService e) {
super(delay);
exec = e;
}
public void run() {
System.out.println(this + " Calling shutdownNow");
exec.shutdownNow();
}
}
class DelayedTaskConsumer implements Runnable {
private DelayQueue<DelayedTask> q;
public DelayedTaskConsumer(DelayQueue<DelayedTask> q) {
this.q = q;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
q.take().run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("DelayedTaskConsumer Finished!");
}
}
class DelayQueueDemo {
public static void chapter21_7() {
Random random = new Random(47);
ExecutorService executorService = Executors.newCachedThreadPool();
DelayQueue<DelayedTask> queue = new DelayQueue<>();
for (int i=0;i<10;i++) {
queue.put(new DelayedTask(random.nextInt(300)));
}
queue.add(new EndSentinel(500,executorService));
executorService.execute(new DelayedTaskConsumer(queue));
}
}
类似时间优先级队列,DelayedTaskConsumer将最紧急的任务从队列取出并运行。
21.7.4 PriorityBlockingQueue 优先级同步队列
这是个优先级队列,具有可阻塞的读取操作。
21.7.5 使用ScheduledExecutor的温室控制器
期望一个事件在一个预定的时间运行。
通过使用schedule()(运行一次任务)或者scheduaAtFixedRate()(每隔规则的时间重复执行任务),你可以将Runnable对象设置为在将来的某个时刻执行。
21.7.6 Semaphore 信号量
正常的锁(concurrent.locks或synchronized锁)在任何时刻都只允许一个任务访问一项资源,而计数信号量允许n个任务同时访问这个资源。
21.9 性能调优
21.9.1 比较Lock和synchronized
一般来说,使用Lock通常比使用synchronized要高效。
并不意味着不应该使用synchronized,原因2点:
1 synchronized互斥方法的方法体一般较小,性能差异不大。当然,当你对性能调优时,应该比较两种方法,做实际的耗时测试。
2 synchronized可读性较高。
21.9.2 免锁容器
Java1.2中,Collections类提供了各种static的同步方法,这种开销是基于synchronized加锁机制的。
Java SE5添加了新的容器,使用更灵巧的技术来消除加锁,提高线程安全。
免锁容器背后的通用策略是:对容器的修改可以与读取操作同时发生,只要读取者只能看到完成修改的结果即可。修改是在容器数据结构某个部分的单独的副本(有时是整个数据结构的副本)上执行的,且这个副本在修改过程中是不可视的。只有当修改完成时,被修改的结构才会自动地与主数据结构进行交换(一个原子操作),之后读取者就可以看到这个修改了。
CopyOnWriteArrayList
CopyOnWriteArraySet
ConcurrentHashMap
ConcurrentLinkedQueue
性能对比:
CopyOnWriteArrayList比Collections.synchronizedList优
ConcurrentHashMap比Collections.synchronizedMap优
21.10 Future 活动对象
活动对象时另一种不同的并发模型。
每个对象都维护这自己的工作器线程和消息队列,并且所有对这种对象的请求都将进入队列排队,任何时候都只能运行其中的一个。
有了活动对象,可以串行化消息而不再是函数方法。
ActiveObjectDemo
class ActiveObjectDemo {
private ExecutorService executorService = Executors.newSingleThreadExecutor();
private void pause(int num) {
try {
MILLISECONDS.sleep(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public Future<Integer> calculateInt(final int x ,final int y) {
return executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("starting " + x + " + " + y);
pause(200);
return x + y;
}
});
}
public void shutdown() {
executorService.shutdown();
}
public static void chapter21_10() {
ActiveObjectDemo activeObjectDemo = new ActiveObjectDemo();
List<Future<?>> results = new CopyOnWriteArrayList<>();
for (int i=0;i<5;i++) {
results.add(activeObjectDemo.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("result: " + f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
results.remove(f);
}
}
}
activeObjectDemo.shutdown();
}
}
例子中,将方法调用排进队列。
newSingleThreadExecutor()产生的单线程(每个对象有自己的线程)维护着自己的无界阻塞队列,且从该队列里取走任务并执行它们直到完成。
在calculateInt()中需要做的时用submit()提交一个Callable对象,以响应方法的调用,这样就可以把方法调用转化为消息。
调用者可以使用Future来发现何时任务完成,并收集实际的返回值。(f.isDone())