线程间的通信
1.wait、notify、notifyAll
何时使用
在多线程环境下,有时候一个线程的执行,依赖于另外一个线程的某种状态的改变,这个时候,我们就可以使用wait与notify或者notifyAll。
wait跟sleep的区别
wait会释放持有的锁,而sleep不会,sleep只是让线程在指定的时间内,不去抢占cpu的资源。
注意点
wait notify必须放在同步代码块中, 且必须拥有当前对象的锁。即不能取得A对象的锁,而调用B对象的wait。哪个对象wait,就得调哪个对象的notify。
notify跟notifyAll的区别
nofity随机唤醒一个等待的线程,notifyAll唤醒所有在该对象上等待的线程
2.使用管道流进行通信
以内存为媒介,用于线程之间的数据传输。
主要有面向字节:【PipedOutputStream、PipedInputStream】、面向字符【PipedReader、PipedWriter】
Reader用于读取内存中的数据
package com.ljessie.sinopsis.communication.pipe;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.util.stream.Collectors;
public class Reader implements Runnable{
private PipedInputStream pis;
public Reader(PipedInputStream pis) {
this.pis = pis;
}
@Override
public void run() {
if(pis != null) {
String collect = new BufferedReader(new InputStreamReader(pis)).lines().collect(Collectors.joining("\n"));
System.out.println(collect);
}
try {
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Main获取控制台输入的数据,写到内存中。
package com.ljessie.sinopsis.communication.pipe;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class Main {
public static void main(String[] args) throws IOException {
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
pos.connect(pis);
new Thread(new Reader(pis)).start();
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(System.in));
pos.write(bufferedReader.readLine().getBytes());
} finally {
bufferedReader.close();
pos.close();
}
}
}
3.Thread.join通信
使用场景:线程A执行到一半,需要一个数据,这个数据需要线程B去执行修改,只有B修改完成之后,A才能继续操作。
package com.ljessie.sinopsis.communication.join;
public class Main {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始运行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
};
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始运行");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
}.start();
}
}
4.ThreadLocal使用
线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。为每个线程单独存放一份变量副本,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。只要线程处于活动状态并且ThreadLocal实例可访问,那么每个线程都拥有对其本地线程副本的隐式引用变量一个线程消失后,它的所有副本线程局部实例受垃圾回收(除非其他存在对这些副本的引用)
一般用的比较多的是
ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
package com.ljessie.sinopsis.communication;
public class ThreadLocalDemo {
static ThreadLocal<Integer> num = new ThreadLocal<Integer>();
/**
* 自增并打印num
*/
private void create() {
Integer myNum = num.get();
myNum++;
System.out.println(Thread.currentThread().getName()+"------>"+myNum);
num.set(myNum);
}
public static void main(String[] args) {
ThreadLocalDemo demo = new ThreadLocalDemo();
for (int i = 1; i < 3; i++) {
int finalI = i;
new Thread() {
public void run() {
num.set(0);
while(true) {
demo.create();
try {
Thread.sleep(finalI*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
}
}