线程通信:多个线程之间进行信息交流与传递。常用于公共数据的通信。
下面我们分别演示一下单消费者、单生产者,多消费者、多生产者的通信。
在演示之前,强调一下:多个线程访问同一个类的不同synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了java的内置锁, 即锁住的是方法所属对象本身。也就是说,多个线程不能同时(并发)访问同一个同步方法,也不能同时(并发)访问不同的同步方法。
消费者、生产者(1-1)示例
1.面包类
public class Breads {
/**
* 面包的id
*/
private int bid;
/**
* 面包的个数
*/
private int num;
/**
* 无参构造
*/
public Breads() {
}
/**
* 有参构造
*/
public Breads(int bid, int num) {
this.bid = bid;
this.num = num;
}
/**
* 生产面包
* synchronized保证值进行生产面包或消费面包(并发线程中串行执行)
*/
public synchronized void pro() {
if (num != 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = num + 1;
bid = bid + 1;
System.out.println(Thread.currentThread().getName() + "生产了一个编号为" + bid + "的面包!个数为" + num);
//2.通知等待的消费线程消费面包(当前线程既然执行到这里了,说明它没有处于等待状态,一般来说就是指当前线程唤醒其它等待线程)
notify();
}
/**
* 消费面包
* synchronized保证值进行生产面包或消费面包(并发线程中串行执行)
*/
public synchronized void consume() {
//1.假设消费线程先进入,判断面包数量为0,进入阻塞等待,强制生产线程生产面包
if (num == 0) {
try {
//等同于this.wait()
wait();
System.out.println("xxxxxxxxxxxxxxxxxxx");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//3.消费线程得到通知,继续执行,num=1-1=0,消费了一个面包
num = num - 1;
System.out.println(Thread.currentThread().getName() + "买了一个编号为" + bid + "的面包!个数还有" + num);
notify();
}
}
2.生产者
public class Producer implements Runnable {
private Breads bre;
/**
* 无参构造
*/
public Producer() {
}
/**
* 初始化公共资源
*/
public Producer(Breads bre) {
this.bre = bre;
}
@Override
public void run() {
p();
}
/**
* 任务:生产面包
*/
private void p() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
bre.pro();
}
}
}
3.消费者
public class Consumer implements Runnable {
private Breads bre;
/**
* 无参构造
*/
public Consumer() {
}
/**
* 初始化公共资源
*/
public Consumer(Breads bre) {
this.bre = bre;
}
@Override
public void run() {
c();
}
/**
* 任务:消费面包
*/
private void c() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
bre.consume();
}
}
}
4.测试类
public class Test {
/**
* 打印结果:
* 生产者生产了一个编号为1的面包!个数为1
* 消费者买了一个编号为1的面包!个数还有0
* 生产者生产了一个编号为2的面包!个数为1
* xxxxxxxxxxxxxxxxxxx
* 消费者买了一个编号为2的面包!个数还有0
* 生产者生产了一个编号为3的面包!个数为1
* 消费者买了一个编号为3的面包!个数还有0
* 生产者生产了一个编号为4的面包!个数为1
* xxxxxxxxxxxxxxxxxxx
* 消费者买了一个编号为4的面包!个数还有0
* 生产者生产了一个编号为5的面包!个数为1
* xxxxxxxxxxxxxxxxxxx
* 消费者买了一个编号为5的面包!个数还有0
* 分析: 调用wait()的线程,在被唤醒以后,会继续执行。
*/
public static void main(String[] args) {
Breads bre = new Breads();
//创建基于同一个面包实例的线程(共享变量)
Producer pro = new Producer(bre);
Consumer con = new Consumer(bre);
//定义生产者线程
Thread t1 = new Thread(pro, "生产者");
//定义消费者线程
Thread t2 = new Thread(con, "消费者");
t1.start();
t2.start();
}
}
消费者、生产者(N-N)示例
1.面包类
public class Breads {
/**
* 面包的id
*/
private int bid;
/**
* 面包的个数
*/
private int num;
/**
* 无参构造
*/
public Breads() {
}
/**
* 有参构造
*/
public Breads(int bid, int num) {
this.bid = bid;
this.num = num;
}
/**
* 生产面包
* synchronized保证只进行生产面包或消费面包(并发线程中串行执行)
* 多个线程访问同一个synchronized方法,只有一个线程能够进入该方法。
* 多个线程访问同一个类的不同synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了java的内置锁, 即锁住的是方法所属对象本身。
*/
public synchronized void pro() {
//while->if:会出现负数消费
//while:循环的作用就是阻塞的线程们被唤醒后,保证只有一个生产线程进行生产,后续执行的线程会重新进入阻塞。
// if (num != 0) {
while (num != 0) {
try {
// System.out.println(Thread.currentThread().getName()+"-----------------pro-wait");
//释放锁,cpu重新调度执行,被唤醒后继续执行,循环进入判断,如果已经生产,其它线程就不能重复生产
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = num + 1;
bid = bid + 1;
System.out.println(Thread.currentThread().getName() + "生产了一个编号为" + bid + "的面包!个数为" + num);
//通知所有等待的消费线程消费面包
notifyAll();
}
/**
* 消费面包
* synchronized保证只进行生产面包或消费面包(并发线程中串行执行)
* 多个线程访问同一个synchronized方法,只有一个线程能够进入该方法。
* 多个线程访问同一个类的不同synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了java的内置锁, 即锁住的是方法所属对象本身。
*/
public synchronized void consume() {
//while->if:会出现负数消费
//while:循环的作用就是阻塞的线程们被唤醒后,保证只有一个消费线程进行消费,后续执行的线程会重新进入阻塞。
// if (num == 0) {
while (num == 0) {
try {
System.out.println(Thread.currentThread().getName()+"-----------------wait");
//释放锁,cpu重新调度执行,被唤醒后继续执行,循环进入判断,如果已经消费,其它线程就不能重复消费
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = num - 1;
System.out.println(Thread.currentThread().getName() + "买了一个编号为" + bid + "的面包!个数还有" + num);
//通知所有等待的生产线程生产面包
notifyAll();
}
/**
* 打印结果:
* 生产者2生产了一个编号为1的面包!个数为1
* 消费者2买了一个编号为1的面包!个数还有0
* 消费者3-----------------wait
* 消费者1-----------------wait
* 生产者1生产了一个编号为2的面包!个数为1
* 消费者1买了一个编号为2的面包!个数还有0
* 消费者3买了一个编号为2的面包!个数还有-1
* 生产者3生产了一个编号为3的面包!个数为0
* ...
*
* 分析:
* num=0,生产线程2生产面包->num=1 【notifyAll无实际效果】
* num=1,生产线程3进入,阻塞等待,等待被唤醒,cpu重新调度执行 【释放锁】
* num=1,消费线程2消费面包->num=0 【notifyAll无实际效果】
* num=0,消费线程3进入->阻塞等待,等待被唤醒,cpu重新调度执行 【释放锁】
* num=0,消费线程1进入->阻塞等待,等待被唤醒,cpu重新调度执行 【释放锁】
* num=0,生产线程1生产面包->num=1 【notifyAll有实际效果,唤醒阻塞的消费线程3、消费线程1,唤醒顺序不保证】
* num=1,消费线程1被唤醒->继续执行,消费面包,num=0 【notifyAll无实际效果】
* num=1,消费线程3被唤醒->继续执行,消费面包,num=-1 【notifyAll有实际效果,唤醒阻塞的生产线程3】
* num=-1,生产线程3被唤醒->继续执行,生产面包,num=0 【notifyAll无实际效果】
* 结论:多消费者多生产者的情况下,出现负数消费,是因为多个消费者之前被阻塞了,还没有消费,一旦被生产者唤醒,就会相继执行。
* 问题:如何解决这个问题?
* 解决:将if判断改为循环判断,这样,被阻塞的线程们,被唤醒以后,继续执行,会重新进入循环判断,保证每生产/消费一次就只能消费/生产一次,想要陆续(重复)消费/生产的线程只能重新(继续)阻塞。
*/
}
2.生产者
public class Producer implements Runnable {
private Breads bre;
/**
* 无参构造
*/
public Producer() {
}
/**
* 初始化公共资源
*/
public Producer(Breads bre) {
this.bre = bre;
}
@Override
public void run() {
p();
}
/**
* 任务:生产面包
*/
private void p() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
bre.pro();
}
}
}
3.消费者
public class Consumer implements Runnable {
private Breads bre;
/**
* 无参构造
*/
public Consumer() {
}
/**
* 初始化公共资源
*/
public Consumer(Breads bre) {
this.bre = bre;
}
@Override
public void run() {
c();
}
/**
* 任务:消费面包
*/
private void c() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
bre.consume();
}
}
}
4.测试类
public class Test {
public static void main(String[] args) {
Breads bre = new Breads();
//创建基于同一个面包实例的线程(共享变量)
Producer pro = new Producer(bre);
Consumer con = new Consumer(bre);
//定义生产者线程
Thread t1 = new Thread(pro,"生产者1");
Thread t2 = new Thread(pro,"生产者2");
Thread t3 = new Thread(pro,"生产者3");
//定义消费者线程
Thread t4 = new Thread(con,"消费者1");
Thread t5 = new Thread(con,"消费者2");
Thread t6 = new Thread(con,"消费者3");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}