线程间通信的几种方式
1.通知等待模式
等待方遵循如下原则。
- 获取对象的锁。
- 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
- 条件满足则执行对应的逻辑。
对应的伪代码如下。
synchronized(对象) { while(条件不满足) { 对象.wait(); } 对应的处理逻辑 }
通知方遵循如下原则。
- 获得对象的锁
- 改变条件
- 通知所有等待在对象上的线程
对应的伪代码如下。
synchronized(对象) {
改变条件
对象.notifyAll();
}
- 使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
- 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
- notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
- notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。
- 从wait()方法返回的前提是获得了调用对象的锁。
2.管道
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。
管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、
PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
对于Piped类型的流,必须先要进行绑定,也就是调用connect()方法,如果没有将输入/输
出流绑定起来,对于该流的访问将会抛出异常。
3.Exchange
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方
4.ThreadLocal
ThreadLocal是如何为每个线程创建变量的副本的:
首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本的值。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
总结一下:
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
3)在进行get之前,必须先set,否则会报空指针异常;
如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
ThreadLocal的应用场景
最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。
项目中的实际使用经验是在@Compont的多个方法中使用统一DBRecoud,为了避免重复查询,在第一次查询完以后保存到ThreadLocal,避免后续冗余查询.同时也避免了并发问题
下面这段代码摘自:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class ConnectionManager { private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { @Override protected Connection initialValue() { Connection conn = null; try { conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/test", "username", "password"); } catch (SQLException e) { e.printStackTrace(); } return conn; } }; public static Connection getConnection() { return connectionHolder.get(); } public static void setConnection(Connection conn) { connectionHolder.set(conn); } }