一.为什么需要线程通讯
线程是操作系统调度的最小单位,有自己的栈空间,可以按照既定的代码逐步的执行,但是如果每个线程间都孤立的运行,那就会造资源浪费。所以在现实中,我们需要这些线程间可以按照指定的规则共同完成一件任务,所以这些线程之间就需要互相协调,这个过程被称为线程的通信。
线程间的通讯定义:多个线程在操作同一份数据时,互相告知自己的状态,避免对同一共享变量的争夺。
二.线程间的通讯方式
线程通讯的方式主要可以分为三种方式,共享内存、消息传递和管道流。
1.共享内存
(1)同步–synchronized
线程同步是线程之间按照⼀定的顺序执⾏,可以使⽤锁来实现达到线程同步,也就是在需要同步的代码块里加上关键字synchronized 。因为⼀个锁同⼀时间只能被⼀个线程持有。
synchronized可以修饰方法或者以同步块,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
基于synchronized实现线程A和线程B通信
线程A执行完,再让线程B执行,使用对象锁实现
public class SynchronizedTest {
static Object obj = new Object();
static class ThreadA implements Runnable{
@Override
public void run() {
synchronized (obj){
for (int i = 0; i < 5; i++) {
System.out.println("ThreadA" + i);
}
}
}
}
static class ThreadB implements Runnable{
@Override
public void run() {
synchronized (obj){
for (int i = 0; i < 5; i++) {
System.out.println("ThreadB" + i);
}
}
}
}
public static void main(String[] args) {
Thread threada = new Thread(new ThreadA());
Thread threadb = new Thread(new ThreadB());
threada.start();
threadb.start();
}
}
运行结果
线程A和线程B需要访问同一个对象lock,谁获得锁,谁就先执行。线程B要等线程A执行完再执行,所以是同步的,这就实现了线程间的通信
(2)信号量 --volatile
在java中,所有堆内存中的所有的数据(实例域、静态域和数组元素)存放在主内存中可以在线程之间共享,一些局部变量、方法中定义的参数存放在本地内存中不会在线程间共享。线程之间的共享变量存储在主内存中,本地内存存储了共享变量的副本。如果线程A要和线程B通信,则需要经过以下步骤
- 线程A把本地内存A更新过的共享变量刷新到主内存中
- 线程B到内存中去读取线程A之前已更新过的共享变量。
为保证线程间的通信必须经过主内存。需要关键字volatile。
volatile保证内存可见性,即多个线程访问内存中的同一个被volatile关键字修饰的变量时,当某一个线程修改完该变量后,需要先将这个最新修改的值写回到主内存,从而保证下一个读取该变量的线程取得的就是主内存中该数据的最新值,这样就保证线程之间的透明性,便于线程通信。
基于volatile关键字实现线程A和线程B的通信
线程A输出0,然后线程B输出1,再然后线程A输出2…
public class VolatileTest {
static volatile int s = 0;
static class ThreadA implements Runnable {
@Override
public void run() {
while (s < 10) {
if (s % 2 == 0) {
System.out.println("ThreadA: " + s);
synchronized (this) {
s++;
}
}
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
while (s < 10) {
if (s % 2 == 1) {
System.out.println("ThreadB: " + s);
synchronized (this) {
s++;
}
}
}
}
}
public static void main(String[] args) {
Thread threada = new Thread(new ThreadA());
Thread threadb = new Thread(new ThreadB());
threada.start();
threadb.start();
}
}
运行结果
volatile 变量需要进⾏原⼦操作。 signal++ 并不是⼀个原⼦操作,所以我们需要使⽤ synchronized 给它“上锁”
2.消息传递
等待/通知机制(wait/notify):一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。等待/通知机制使⽤的是使⽤同⼀个对象锁,如果你两个线程使⽤的是不同的对象锁,那它们之间是不能⽤等待/通知机制通信的。
wait()当前线程释放锁并进入等待(阻塞)状态,notify()唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后继续竞争锁notifyAll()唤醒所有正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后继续竞争锁
notify()和notifyAll()的区别:
notify()⽅法会随机叫醒⼀个正在等待的线程,⽽notifyAll()会叫醒所有正在等待的线程。
基于Object 类的 wait() ⽅法和 notify() ⽅法实现
public class WaitandNotify {
static boolean flag = true;
static Object obj = new Object();
static class ThreadA implements Runnable {
@Override
public void run() {
synchronized (obj) {
while (flag){
System.out.println("flag为true,不满足条件,继续等待");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("flag为false,我要从wait状态返回继续执行了");
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
synchronized (obj) {
while (flag){
obj.notifyAll();
System.out.println("设置flag为false,我发出通知了,但是我不会立马释放锁");
flag = false;
}
}
}
}
public static void main(String[] args) {
Thread threada = new Thread(new ThreadA());
Thread threadb = new Thread(new ThreadB());
threada.start();
threadb.start();
}
}
运行结果
3.管道流
管道输入/输出流用于线程之间的数据传输,传输的媒介为管道(内存)
管道输入/输出流的体现:
基于字节流:PipedOutputStrean、PipedInputStrean
基于字符:PipedReader,PipedWriter
public class WaitandNotify {
static boolean flag = true;
static Object obj = new Object();
static class ReaderThread implements Runnable{
private PipedReader in;
public ReaderThread(PipedReader in){
this.in=in;
}
@Override
public void run() {
System.out.println("this is a reader");
int receice=0;
try {
while ((receice=in.read())!=-1){
System.out.println("read "+(char) receice);
}
}catch (IOException ex){
ex.printStackTrace();
}
}
}
static class WriterThread implements Runnable{
private PipedWriter out;
public WriterThread(PipedWriter out){
this.out=out;
}
@Override
public void run() {
System.out.println("this is a writer");
try {
out.write("write A");
}catch (IOException ex){
ex.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException {
PipedWriter writer = new PipedWriter();
PipedReader reader = new PipedReader();
writer.connect(reader);
Thread threada = new Thread(new ReaderThread(reader));
Thread threadb = new Thread(new WriterThread(writer));
threada.start();
threadb.start();
}
}
运行结果