- 相互通信的互斥线程
1. 最困难的线程编程是线程之间需要相互传递数据。
2. 典型的wait/notify线程同步问题。(1)当一个线程需要等待其他线程提供数据,而数据尚未就绪时,线程将会因等待数据而暂停执行。(2)另一种情况是当数据就绪时,需要通知另一个线程接收数据。
3. 当同一个类中的同步方法需要相互通信时,应使用wait/notify机制进行同步控制。最常用的应用场合是解决生产者/消费者问题(Producer/Consumer),即以个线程生成数据,另一个线程消费(处理)数据。
4. 消费者线程获取互斥锁之后,将会检查缓冲区中的数据是否已经就绪。生成者线程获取互斥锁之后,将会检查缓冲区中是否还有空间,从而能够继续写入数据。
5. 正确的做法是采用wait()和notify()方法。(1)调用wait()方法意味着,即使消费者线程拥有互斥锁,在得到期望的数据之前,也无法做进一步的处理,因而只能释放互斥锁,并使自己处于等待状态。(2)调用notify()方法意味着,一旦生产者线程提供了消费者线程等待的数据,它将通知消费者线程数据已经就绪,然后释放互斥锁,并使自己处于等待状态。
6. 使用wait()和notify()方法来解决生产者/消费者(伪代码)
// 生产者线程 while(buffer_full) wait() produce_data() notify() // 消费者线程 while(no_data) wait() consume_the_data() notify()
7. 主程序。功能是实例化生产者和消费者两个线程,使它们开始运行。
8. 回调(Callback)。把对象传递给构造函数的技术使构造函数能够得到该对象的引用,以便以后能够把数据再传递回去。
public class WaitNotify { private String[] buffer = new String[8]; // produce index private int pi = 0; // get index private int gi = 0; private int sequence = 0; Object o = new Object(); public static void main(String[] args) { new WaitNotify().start(); } void start() { new Producer().start(); new Consumer().start(); } private final String id() { return String.valueOf(sequence++); } class Consumer extends Thread { public void run() { String result; while(true) { System.out.println("invoke consume"); synchronized (o) { result = consume(); } System.out.println("cosumed==>"+result); System.out.println("pi="+pi+", gi="+gi); try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } String consume() { int index; String result; while(pi == gi) { System.out.println("Consumer wait..."); try { o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } index = gi++ % buffer.length; result = buffer[index]; o.notify(); return result; } } class Producer extends Thread { public void run() { while(true) { System.out.println("invoke produce"); synchronized (o) { produce(); } } } void produce() { int index; while(pi - gi + 1 > buffer.length) { System.out.println("Producer wait..."); try { o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } index = pi++ % buffer.length; buffer[index] = id(); System.out.println("produced["+index+"]==>"+buffer[index]); System.out.println("pi="+pi+", gi="+gi); o.notify(); } } }
9. void java.lang.Object.wait() throws InterruptedException
调用该对象的wait()方法会迫使当前线程进入等待状态,除非其他某个线程调用该对象的notify()方法或notifyAll()方法。
当前线程必须拥有该对象的互斥锁。该线程释放该对象的互斥锁,进入等待状态,除非另外某个线程通知该线程进入唤醒状态。
synchronized (obj) { while (<condition does not hold>) obj.wait(); ... // Perform action appropriate to condition }
如果当前线程没有获取到该对象的互斥锁,抛出异常IllegalMonitorStateException;如果该线程在进入等待状态时或之前,其他某个线程中断该线程,抛出异常InterruptedException,该线程的中断状态被清空。
10. notify()和notifyAll()方法之间的差别是,notifyAll()方法将唤醒等待当前对象的所有线程。
11. wait()方法有三种形式,其中两个方法增加了等待的超时值,一旦达到超时值指定的毫秒数,或毫秒数加微秒数,等待结束。
12. wait/notify同步机制的目的是避免对象在真正能够运行之前竞争系统资源。
13. 通过调用线程的interrupte()方法,一个线程可以中断另一个暂停的线程。这将使中断的线程被唤醒,然后进入异常处理。
14. 线程必须知道自己是由于notify被唤醒,还是由于interrupt被唤醒。sleep()、join()或wait()等都可能由于被中断而抛出异常。
15. 线程的interrupt()方法用于把中断状态设置为true,使用线程的interrupted()方法可以查询和清除自己的中断状态,也可以使用线程的isInterrupted()方法查询任何线程的中断状态,interrupt()方法不会唤醒一个正在等待获取互斥锁的线程。
16. 使用同步代码块,系统仍然可能进入死锁状态。死锁意味着拥有资源和申请资源的若干线程陷入循环的相互依赖和相互等待状态。
17. 关键字volatile告诉编译器,若干线程可以同时访问这一数据,因此这个数据必须总是最新的值。关键字volatile目的是访问位于内存的实时时钟一类的对象。但是在当前版本的运行时库没有任何地方使用volatile关键字。
18. 管道用于线程间传输数据。一个线程把数据写入管道,另一个线程从管道中读取数据。PipedInputStream用于从管道中读取字节数据,PipedOutStream用于把字节数据写入管道中。使用管道方法不会出现数据竞争。
19. 管道的缺陷。线程之间的管道是由共享内存中的缓冲区实现的,而缓冲区的容量是有限制的。如果输入和输出的速率不同,较快的线程将会因等待较慢的线程而处于封锁状态。相对wait/notify同步机制,管道使得线程之间的通信变得简单。
20. 如果程序的设计过分依赖于两个线程之间的交替轮转,可能会造成死锁。
21. 线程本地存储区(thread local storage)意味着只要能访问线程就可以访问其本地存储区中的数据,只能容纳对每个线程来说惟一的数据。典型应用是存储线程的状态信息,如用户ID、会话ID或交易ID等。
22. 程序员可以为线程本地存储提供任何对象数据,
public class TLS { public static void main(String[] args) { for(int i=0;i<5;i++) { new ThreadWithTLS().start(); } } } class MyThreadLocal extends ThreadLocal<Integer> { private static int id = 0; @Override public Integer get() { return super.get(); } @Override protected synchronized Integer initialValue() { return id++; } } class ThreadWithTLS extends Thread { private static MyThreadLocal tls = new MyThreadLocal(); public void run() { System.out.println("thread local value is " + tls.get()); } }
23. 线程本地存储采用弱引用技术,拥有一张数据表,其中包含线程及其相应的本地存储数据项的映射关系。
24. java.util.concurrent包。提供了若干线程实用程序类和一些新的集合框架。按照一组设计模式实现了Java线程。主要用于开发大型复杂的服务器系统。通过改善某些重要的底层支持,实现了线程的优化,支持线程安全的编程。