【java多线程】线程间通信-利用wait和notify轮流按序打印奇数和偶数
线程之间通信,可以用wait和notify来实现。
(一)对wait的理解
wait是object的一个方法,表示是当前执行代码的线程进行等待,且会释放当前的锁,满足一定条件时被唤醒,然后重新尝试获取锁。这样说明,必须先持有锁,才能执行wait语句,所以wait一般是用在synchronized代码块中。
wait如果需要结束等待,可以通过其他线程调用该对象的notify方法,也可以在wait时设置等待超时时间,或者通过其他线程调用等待线程的interrupt方法,导致wait抛出interruptedException异常
(二)对notify的理解
notify是唤醒等待的线程,notify用来唤醒那些等待当前对象持有的锁的其他线程。如果只需要唤醒1个等待的线程,直接用notify即可。如果需要从多个等待的线程中,随机唤醒1个,则需要用notifyAll,同样的notify也需要在synchronized同步代码块中使用到
(三)下面我们通过一个例子来说明wait和notify的使用,并讲述线程间是如何通信的。这个例子的代码如下,这个例子实现了用多线程轮流按序来打印奇数和偶数。
package com.company;public class JishuAndOuShuTest {public static void main(String[] args) {Data data = new Data();//线程数量int THREAD_COUNT = 3;Thread[] threadJiShuArray = new Thread[THREAD_COUNT];Thread[] threadOuShuArray = new Thread[THREAD_COUNT];//初始化奇数线程和偶数线程for (int i = 0; i < THREAD_COUNT; i++) {//奇数打印线程数组threadJiShuArray[i] = new Thread(() -> {while (data.getCount() <= 100) {data.printJiShu();}}, "奇数线程" + i);//偶数打印线程数组threadOuShuArray[i] = new Thread(() -> {while (data.getCount() <= 100) {data.printOuShu();}}, "偶数线程" + i);}//启动奇数线程和偶数线程for (int i = 0; i < THREAD_COUNT; i++) {threadJiShuArray[i].start();threadOuShuArray[i].start();}}
}class Data {/*** 需要打印的数据*/private int count = 1;public synchronized int getCount() {return count;}/*** 打印奇数*/public synchronized void printJiShu() {if (count % 2 == 1) {System.out.println(Thread.currentThread().getName() + "打印" + count);count++;this.notifyAll();} else {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}/*** 打印偶数*/public synchronized void printOuShu() {if (count % 2 == 0) {System.out.println(Thread.currentThread().getName() + "打印" + count);count++;this.notifyAll();} else {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}
}
运行结果如下:
奇数线程0打印1
偶数线程0打印2
奇数线程0打印3
偶数线程0打印4
奇数线程0打印5
偶数线程0打印6
奇数线程1打印7
偶数线程0打印8
奇数线程0打印9
偶数线程1打印10
奇数线程1打印11
偶数线程0打印12
奇数线程2打印13
偶数线程1打印14
奇数线程1打印15
偶数线程2打印16
奇数线程2打印17
偶数线程1打印18
奇数线程1打印19
偶数线程2打印20
奇数线程2打印21
偶数线程1打印22
奇数线程1打印23
偶数线程2打印24
奇数线程2打印25
偶数线程1打印26
奇数线程1打印27
偶数线程2打印28
奇数线程2打印29
偶数线程1打印30
奇数线程1打印31
偶数线程2打印32
奇数线程2打印33
偶数线程1打印34
奇数线程1打印35
偶数线程2打印36
奇数线程2打印37
偶数线程1打印38
奇数线程1打印39
偶数线程2打印40
奇数线程2打印41
偶数线程1打印42
奇数线程1打印43
偶数线程2打印44
奇数线程2打印45
偶数线程1打印46
奇数线程1打印47
偶数线程2打印48
奇数线程2打印49
偶数线程1打印50
奇数线程1打印51
偶数线程2打印52
奇数线程2打印53
偶数线程1打印54
奇数线程0打印55
偶数线程2打印56
奇数线程1打印57
偶数线程1打印58
奇数线程0打印59
偶数线程2打印60
奇数线程1打印61
偶数线程1打印62
奇数线程0打印63
偶数线程2打印64
奇数线程1打印65
偶数线程1打印66
奇数线程0打印67
偶数线程2打印68
奇数线程1打印69
偶数线程1打印70
奇数线程0打印71
偶数线程2打印72
奇数线程1打印73
偶数线程1打印74
奇数线程0打印75
偶数线程2打印76
奇数线程1打印77
偶数线程1打印78
奇数线程0打印79
偶数线程2打印80
奇数线程1打印81
偶数线程1打印82
奇数线程0打印83
偶数线程2打印84
奇数线程1打印85
偶数线程1打印86
奇数线程0打印87
偶数线程2打印88
奇数线程1打印89
偶数线程1打印90
奇数线程0打印91
偶数线程2打印92
奇数线程1打印93
偶数线程1打印94
奇数线程0打印95
偶数线程2打印96
奇数线程1打印97
偶数线程1打印98
奇数线程0打印99
偶数线程2打印100
Disconnected from the target VM, address: '127.0.0.1:61821', transport: 'socket'Process finished with exit code 0
在上面代码中,我们能看到wait和notifyAll是用在synchronized同步方法中,我们设置了3个奇数打印线程和3个偶数打印线程,来轮流交替打印1-100之间的奇数和偶数。
代码运行流程如下:
首先会判断当前数是否小于100,如果小于100,则奇数线程和偶数线程都会开始运行。当奇数线程运行时,在printJiShu同步方法中,会判断当前数据是否为奇数,如果是奇数,则会打印出来,并且再自增1,因为数据就变成了偶数,然后当前线程就通过notify唤醒其他线程,然后此时数据还是小于100,奇数线程会再次执行while语句中的代码块,因为当前数据是偶数了,会走else分支,然后当前线程通过wait方法,进入等待状态。而偶数线程的执行过程也类似,当判断当前数字小于100,偶数线程会开始运行,然后判断当前数字是否为偶数,如果是偶数,则会打印出来,并执行自增1的操作,然后又会唤醒其他出于wait的线程。此时可能唤醒奇数线程,如果奇数线程被唤醒,因为次数数字又变成了奇数,然后又会执行奇数打印,然后再自增1,之后再进行唤醒其他线程,如此循环。直至打印的数字变成100,然后跳出while循环,从而线程执行结束。