线程间通信
概述
线程间的通信有以下几种方式:
- volatile 和 synchronized 关键字
- 等待 / 通知机制
- 管道输入/ 输出流
- Thread.join() 的使用
- ThreadLocal
volatile 与 synchronized
volatile 与 synchronized 保证可见性,可见性如果用内存模型来阐述,那就是在变量的写的时候,把把该变量同步到主内存,其他线程通过嗅探主内存的该变量值是否变化,在变化的时候更新自己的工作内存,这样就完成了线程之间的交互。
等待 / 通知机制
等待 / 通知的相关方法是任意 Java 对象都具备的,因为这些方法被定义在所有对象的超类 java.lang.Object 上:
方法名称 | 描述 |
---|---|
notify() | 通知一个在对象上等待的线程,使其从 wait() 方法返回,而返回的前提是该线程获取到了对象的锁 |
notifyAll() | 通知所有等待在该对象上的线程 |
wait() | 调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用 wait() 方法后,会释放对象的锁 |
wait(long) | 超时等待一段时间,这里的参数时间单位是毫秒,如果没有通知就超时返回 |
等待 / 通知机制,是指一个线程 A 调用了对象 O 的 wait 方法进入等待状态,而另外一个线程 B 调用了对象 O 的 notify 或者 notifyAll 方法,线程 A 收到通知后从对象 O 的 wait 方法返回,进而执行后续操作。上诉两个线程通过对象 O 完成交互,而对象上的 wait 方法和 notify / notifyAll 的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
在使用以上方法需要注意几点:
- 使用 wait、notify、notifyAll 时需要先对调用对象加锁
- 调用 wait 方法后,线程状态由 RUNNING 变为 WAITING,并将当前线程放置到对象的等待队列
- notify 或 notifyAll 方法调用之后,等待线程依旧不会从 wait 返回,需要调用 notify 或 notifyAll 的线程释放锁之后,等待线程才有机会从 wait 方法
- notify 方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而 notifyAll 方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由 WAITING 变为 BLOCKED
- 从 wait 方法返回的前提是获得了调用对象的锁
管道输入 / 输出流
管道输入 / 输出流和普通的文件输入 / 输出流或者网络输入 / 输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。
管道输入 / 输出流主要包括了如下四种具体实现:PipedOutputStream、PipedInputStream、PipedReader、PipedWriter,前两种面向字节,而后两种面向字符。
public class PipedDemo {
public static void main(String[] args) throws IOException {
PipedReader pipedReader = new PipedReader();
PipedWriter pipedWriter = new PipedWriter();
//将输入输出流进行链接,否则会抛出 IOException
pipedWriter.connect(pipedReader);
Thread printThread = new Thread(new PrintRunnable(pipedReader));
printThread.start();
int receive = 0;
try {
while ((receive = System.in.read()) != -1) {
pipedWriter.write(receive);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
pipedWriter.close();
}
}
static class PrintRunnable implements Runnable {
private PipedReader pipedReader;
public PrintRunnable(PipedReader pipedReader) {
this.pipedReader = pipedReader;
}
@Override
public void run() {
int receive = 0;
try {
while ((receive = pipedReader.read()) != -1) {
System.out.println("输出:" + (char) receive);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* Demo
* 输出:D
* 输出:e
* 输出:m
* 输出:o
* 输出
*/
Thread.join() 的使用
如果一个线程 A 执行了 thread.join() 语句,其含义是:当前线程 A 等待 thread 线程终止之后才从 thread.join() 返回。线程 Thread 除了提供 join 方法之外,还提供了 join(long millis)等两个具备超时特性的方法。这两个超时方法表示,如果线程 thread 在给定的超时时间里没有终止,那么将会从该超时方法中返回。
public class JoinDemo {
public static void main(String[] args) {
Thread previous = Thread.currentThread();
for (int i = 0; i < 10; i++) {
//每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
Thread thread=new Thread(new MyRunnable(previous),String.valueOf(i));
thread.start();
previous=thread;
}
}
static class MyRunnable implements Runnable{
private Thread thread;
public MyRunnable(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" terminate ");
}
}
}
/**
* 0 terminate
* 1 terminate
* 2 terminate
* 3 terminate
* 4 terminate
* 5 terminate
* 6 terminate
* 7 terminate
* 8 terminate
* 9 terminate
*/
从输出可以看出,每个线程终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从 join 方法返回,这里涉及了等待 / 通知记住(等待前驱线程结束,接受前驱线程结束的通知)。
当线程终止时,会调用线程自身的 notifyAll 方法,会通知所有等待在该线程对象上的线程。
ThreadLocal 的使用
ThreadLocal,即线程变量,是一个以 ThreadLocal 对象为键、任意对象为值的存储结构。这个结构被依附在线程上,也就是说一个线程可以根据一个 ThreadLocal 对象查询到绑定在这个线程上的一个值。
可以通过 set(T) 方法来设置一个值,在当前线程下再通过 get() 方法获取到原先设置的值。
public class ThreadLocalDemo {
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<>() {
@Override
protected Long initialValue() {
return System.currentTimeMillis();
}
};
public static final void begin() {
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
public static final long end() {
return System.currentTimeMillis() - TIME_THREADLOCAL.get();
}
public static void main(String[] args) throws InterruptedException {
ThreadLocalDemo.begin();
Thread.sleep(2000);
System.out.println(ThreadLocalDemo.end());
}
}
//输出 2003