9.线程生产者消费者
.生产者和消费者的synchronized 版本以及虚假唤醒问题
先看一个场景:
一个卖面的面馆,有一个做面的厨师和一个吃面的食客,需要保证,厨师做一碗面,食客吃一碗面,
不能一次性多做几碗面,更不能没有面的时候吃面;
按照上述操作,进行十轮做面吃面的操作。
样例代码:
public class Juc_test {
public static void main(String[] args){
Noodels noodels=new Noodels();
//重点1:创建两个线程:1厨子造面 2.食客吃面,先做10碗
new Thread(()->{
try {
for (int i=0;i<=10;i++){
noodels.makeNoodeles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"厨子").start();
new Thread(()->{
try {
for (int i=0;i<=10;i++){
noodels.eatNoodeles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"食客").start();
}
}
class Noodels{
//面的数量
private int num=0;
/*
做面方法
*/
public synchronized void makeNoodeles() throws InterruptedException {
if(num>0){
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
//面已做好,唤醒食客
this.notifyAll();
}
public synchronized void eatNoodeles() throws InterruptedException {
if (num==0){
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
this.notifyAll();
}
}
运行结果如下:很满意,达到了效果,做一碗吃一碗的水平!
厨子造面了!当前面数:1
食客造面了!当前面数:0
厨子造面了!当前面数:1
食客造面了!当前面数:0
厨子造面了!当前面数:1
食客造面了!当前面数:0
厨子造面了!当前面数:1
问题来了:如果多个线程呢,即两个厨子,两个食客呢??改造代码,只需要在测试类里多加几个线程!
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"厨子A").start();
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"厨子B).start();
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"食客A").start();
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"食客B").start();
运行结果:打破了吃一碗造一碗的逻辑了,并且明明加了synchronized锁,为什么会这样呢?这里就涉及到了线程的虚假唤醒!
厨子A造面了!当前面数:1
食客A造面了!当前面数:0
厨子B造面了!当前面数:1
厨子A造面了!当前面数:2
厨子B造面了!当前面数:3
...
虚假唤醒
上面的问题就是"虚假唤醒"。
当我们只有一个厨师一个食客时,只能是厨师做面或者食客吃面,并没有其他情况;
但是当有两个厨师,两个食客时,就会出现下面的问题:
1.初始状态
2.厨师A得到操作权,发现面的数量为0,可以做面,面的份数+1,然后唤醒所有线程;
3.厨师B得到操作权,发现面的数量为1,不可以做面,执行wait操作;
4.厨师A得到操作权,发现面的数量为1,不可以做面,执行wait操作;
5.食客甲得到操作权,发现面的数量为1,可以吃面,吃完面后面的数量-1,并唤醒所有线程;
6.此时厨师A得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程;
7.此时厨师B得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程;
if(num != 0){
this.wait();
}
出现虚假唤醒的原因:
是从阻塞态到就绪态再到运行态没有进行判断(num的判断),直接进行下述的+1或-1,
所以我们只需要让其每次得到操作权时都进行判断就可以了;
解决办法:将if判断改为while,每次都会判断,唤醒以后都先判断条件是否成立!
while(num != 0){
this.wait();
}
JUC版的生产者和消费者
使用Lock和Condition实现上述生产者和消费者问题:
样例代码:
class Noodels{
//面的数量
private int num=0;
//重点1:获取lock锁(可重入锁)
Lock lock = new ReentrantLock();
//重点2:获取condition对象
Condition condition = lock.newCondition();
/*
做面方法
*/
public void makeNoodeles() throws InterruptedException {
//重点3:加锁,不用synchronized关键字
lock.lock();
try {
while (num>0){
//重点4:使用condition.await来阻塞队列
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
//重点5:condition.signalAll();来解锁
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//重点5:解锁
lock.unlock();
}
}
public synchronized void eatNoodeles() throws InterruptedException {
lock.lock();
try {
while (num==0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
问题:如何精确唤醒呢?例如A,B,C,D四个线程,如何精确的执行顺序A->B->C->D
实现如下:
/**
* Bruk.liu
* A执行完调用B,B执行完调用C,C执行完调用A
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C").start();
}
}
//资源类
class Data3{
//创建Lock锁
private Lock lock = new ReentrantLock();
//同步监视器,创建三个监视器
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int number = 1;//1就是A执行,2就是B执行
public void printA(){
lock.lock();
try {
while(number != 1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"===>AAAAAAAAA");
//唤醒指定线程
number = 2;
//调用指定监视器,唤醒指定线程
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(number != 2){
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"===>BBBBBBBB");
//唤醒指定线程
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while(number != 3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"===>CCCCCCCCC");
//唤醒指定线程
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8锁现象
1.synchronized锁的对象是方法调用者!
2.static synchronized...锁的是class这个类!
示例如下:
1.两种方法都sendMessage/call都加上锁synchronized,锁的是方法的调用者!
public class Juc_Test_Lock {
public static void main(String[] args) throws InterruptedException {
//同一个资源对象Phone,所以所的是同一资源对象phone
Phone phone=new Phone();
new Thread(()->{phone.sendMessage();},"发短信!").start();
//重点1:两个线程间休眠4秒,能明显看出哪个线程先执行
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone.call();},"打电话!").start();
}
}
class Phone{
//重点2:方法加锁,并在打印前加入休眠,能明显看出哪个先执行!
public synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送短信");
}
public synchronized void call(){
System.out.println("打电话!");
}
}
输出:因为两个方法都加了synchronized,锁的是方法的调用者,方法调用者是同一对象phone,所以锁是同一把锁!
发送短信
打电话!
2.两个对象:
创建两个对象,因为synchronized锁的是方法调用者,此处是 phone1和phone2,不是同一对象,所以线程2不会等待线程1执行完毕
Phone phone1=new Phone();
Phone phone2=new Phone();
new Thread(()->{phone1.sendMessage();},"发短信!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone2.call();},"打电话!").start();
结论:
打电话!
发送短信
3.static synchronized:锁定的是这个类模板
代码示例如下:
public class Juc_Test_Lock {
public static void main(String[] args) throws InterruptedException {
Phone phone=new Phone();
new Thread(()->{phone.sendMessage();},"发短信!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone.call();},"打电话!").start();
}
}
class Phone{
重点1:方法上使用static synchronized修饰
public static synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送短信");
}
public static synchronized void call(){
System.out.println("打电话!");
}
}
输出:static synchronized:锁定的是这个类模板,线程2必须等待线程1执行完毕后才能执行!因为这是一把锁
发送短信
打电话!
4.上述情况下,方法加了static synchronized,但是是两个对象
重点1:创建了两个对象去执行static synchronized修饰的方法
Phone phone1=new Phone();
Phone phone2=new Phone();
new Thread(()->{phone1.sendMessage();},"发短信!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone2.call();},"打电话!").start();
输出:发现线程2必须等线程1执行完毕后才执行,因为static synchronized锁的是类模板,是一般锁!
发送短信
打电话!
5.staic synchronized和synchronized混用:
public class Juc_Test_Lock {
public static void main(String[] args) throws InterruptedException {
重点1:单个对象
Phone phone=new Phone();
new Thread(()->{phone.sendMessage();},"发短信!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone.call();},"打电话!").start();
}
}
class Phone{
重点2:使用了static synchronized
public static synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送短信");
}
重点3:使用synchronized
public synchronized void call(){
System.out.println("打电话!");
}
}
输出:因为staic synchronized和synchronized锁的是不同的对象,synchronized锁的是方法调用者phone对象,而staic synchronized锁的是Phnoe类模板
所以是两把不同的锁,所以线程2不会等待1执行结束才执行!
打电话!
发送短信