java并发编程基础(四)-线程之间的协作
本文为学习《thinking in java》第21章的相关笔记
Object.wait()和Object.notifyAll()
-
忙等待:占用CPU时间并且不断进行空循环
-
wait()会在等待外部世界产生变化的时候将任务挂起,并且只有notify()或notifyAll()发生变化时,这个任务才会被唤醒
-
调用sleep()的时候锁并没有释放,调用yield()也是同样的情况
-
而一个任务调用wait的时候,线程的执行被挂起,对象上的锁被释放
-
只能在同步控制块中调用wait(),notify(),notifyAll()方法,即任务在调用这些方法前必须获得对象的锁
-
必须使用使用一个检查感兴趣的条件的while循环来包围wait(),因为有可能在A线程被唤醒前,B线程先被唤醒并且改变了条件,即本质上就是一直检查感兴趣的特定条件,并在条件不满足的情况下返回到wait()中
while(expression) wait();
-
notify()和notifyAll()
- 前者只会唤醒众多等待同一个锁任务中的一个任务,后者会唤醒等待的所有任务
使用显式的Lock和Condition对象
-
同上面提及到的方法作用类似,使用互斥并允许任务挂起的基本类为Condition,可以通过Condition来调用await()来挂起一个任务;当外部条件发生变化,意味着某一个任务应该继续执行,可以通过调用signal()来通知这一个任务,或者通过调用signalAll()来通知所有的任务
-
构造Condition的方法
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition();
单个的Lock将产生一个Condition对象,这个对象被用来管理任务之间的通讯,但是Condition对象不包括任何有关处理状态的信息,因此我们需要管理额外的表示处理状态的信息,例如
boolean hungry
生产者-消费者队列
- 使用阻塞队列BlockingQueue,该接口有大量的标准实现
- LinkedBlockingQueue:无界队列
- ArrayBlockingQueue:有界队列
死锁
-
简化版的哲学家进餐的死锁和避免死锁的方法(Jerry类中注释的代码)
public class PhilosopherDeadLock { /** * 筷子类,代表临界资源 * 筷子只有两个状态: 空闲和占用 * 这两个状态之间的装换需要两个方法 */ static class Chopstick { private boolean taken; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void take() { lock.lock(); try { while (taken) { condition.await(); // 筷子已经被占用,只好等着,等别人通知 } taken = true; // 表示自己拿了筷子 } catch (InterruptedException ex) { } finally { lock.unlock(); } } public void drop() { lock.lock(); try { taken = false; // 放下筷子 condition.signalAll(); // 通知别人 } finally { lock.unlock(); } } } static class Philosopher { protected Chopstick left; protected Chopstick right; public Philosopher(Chopstick left, Chopstick right) { this.left = left; this.right = right; } } static class Tom extends Philosopher implements Runnable { public Tom(Chopstick left, Chopstick right) { super(left, right); } @Override public void run() { try { left.take(); TimeUnit.SECONDS.sleep(2); // 占用左筷子,睡眠足够的时间让对方拿 right.take(); System.out.println("tom is eating!"); right.drop(); left.drop(); System.out.println("tom finished eating!"); } catch (InterruptedException e) { e.printStackTrace(); } } } static class Jerry extends Philosopher implements Runnable { public Jerry(Chopstick left, Chopstick right) { super(left, right); } @Override public void run() { try { right.take(); TimeUnit.SECONDS.sleep(2); left.take(); // 破坏死锁第四个条件循环等待 // left.take(); // TimeUnit.SECONDS.sleep(2); // 占用左筷子,睡眠足够的时间让对方拿 // right.take(); System.out.println("Jerry is eating!"); left.drop(); right.drop(); System.out.println("Jerry finished eating!"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Chopstick left = new Chopstick(); Chopstick right = new Chopstick(); ExecutorService service = Executors.newFixedThreadPool(2); service.execute(new Tom(left, right)); service.execute(new Jerry(left, right)); service.shutdown(); } }
-
死锁发生的四个条件,缺一不可
- 互斥:即资源不能同时共享,例如一根筷子不可以同时两人使用吧
- 占有和等待:至少一个任务拿了一部分资源,并且等待着被其他任务占有的资源,比如Tom拿着左边的一根筷子,等待着Jerry拿着的右边的一根筷子
- 资源不可抢占:例如Tom不能抢了Jerry手上的一根筷子
- 环路(循环)等待:死锁发生时,系统一定有两个或者以上的进程形成一条环路,该环路中每一个进程(或线程)都在等待着下一个进程(或线程)占有的资源。例如Tom等着Jerry手上的右边一根,Jerry等着Tom手上的左边一根,形成环路。