传统的生产者消费者问题,防止虚假唤醒

synchronized锁和lock锁的区别

        1. synchronized是Java内置的关键字,lock是一个Java类(JUC下的接口 )
        2. synchronized是无法判断获取锁的状态,lock可以判断获取锁的状态
        3. synchronized是全自动的,会自动释放锁,lock是手动的,必须手动释放锁(unLock),如果不释放锁,会造成死锁
        4. synchronized比如有两个线程,线程1获得锁,阻塞,线程2会一直等,lock锁就不一定会一直等待, 可以用tryLock()方法尝试获取锁
  1. synchronized默认是可重入锁,不可以中断的,非公平,由于它是Java关键字,不能进行修改,lock锁也是可重入锁,可以判断锁是否中断,可以自己设置公平锁或者不公平锁(参数为true即可变为公平锁,默认不传参且为不公平锁),使用起来比synchronized更加灵活方便
  2. synchronized适合少量的代码同步问题.lock锁适合大量的同步代码
  1. 传统的生产者消费者问题,防止虚假唤醒

线程之间的通信问题 生产者和消费者问题

生产者和消费者代码编写思路:

判断是否等待 进行业务处理 通知其他线程

判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程

传统(synchronized)线程通信代码简单实现,线程交替执行 A B同时操作同一个变量

package com.wyh.pc;
/**
 * @program: JUC
 * @description: 线程通信
 * @author: 魏一鹤
 * @createDate: 2022-02-12 21:12
 **/
/**
  *线程之间通信问题:生产者和消费者  等待唤醒,通知唤醒
 * 线程交替执行 A B同时操作同一个变量
**/
//生产者和消费者代码编写思路:判断是否等待 进行业务处理 通知其他线程
//判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程
public class A {
public static void main(String[] args){
//线程操作资源类
        //创建资源类
        Data data=new Data();
//多线程处理 把资源抛给线程去执行 这里采用lambda表达式简化代码 ,第二个参数是现成名称
        new Thread(()->{
//从10循环+1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程A").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程B").start();
    }
}
//静态资源  独立耦合的 必须降低耦合性
class Data{
// 属性 数字变量
    private  int num=0;
//方法 数字+1 自增1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void increment() throws InterruptedException {
if(num!=0){
//值为0的时候等待,值为1的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num++;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我+1完毕了
        this.notifyAll();
    }
//方法 数字-1 自减1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void decrement() throws InterruptedException {
if(num==0){
//值为1的时候等待,值为0的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num--;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我-1完毕了
        this.notifyAll();
    }
}

通过打印发现,实现了简单的线程通信交互执行

传统的生产者消费者问题,防止虚假唤醒

不过,以上代码是有问题的,现在A,B两个线程可以正常执行,那么如果有更多线程呢?

现在再加上两个线程执行,也就是两个线程加两个线减,执行代码还会和最开始两个线程执行的结果一样吗?

package com.wyh.pc;
/**
 * @program: JUC
 * @description: 线程通信
 * @author: 魏一鹤
 * @createDate: 2022-02-12 21:12
 **/
/**
  *线程之间通信问题:生产者和消费者  等待唤醒,通知唤醒
 * 线程交替执行 A B同时操作同一个变量
**/
//生产者和消费者代码编写思路:判断是否等待 进行业务处理 通知其他线程
//判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程
public class A {
public static void main(String[] args){
//线程操作资源类
        //创建资源类
        Data data=new Data();
//多线程处理 把资源抛给线程去执行 这里采用lambda表达式简化代码 ,第二个参数是现成名称
        new Thread(()->{
//从10循环+1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程A").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程B").start();
 //新加入两个线程
 new Thread(()->{
            //从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程C").start();
        new Thread(()->{
            //从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程D").start();
    }
}
//静态资源  独立耦合的 必须降低耦合性
class Data{
// 属性 数字变量
    private  int num=0;
//方法 数字+1 自增1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void increment() throws InterruptedException {
if(num!=0){
//值为0的时候等待,值为1的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num++;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我+1完毕了
        this.notifyAll();
    }
//方法 数字-1 自减1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void decrement() throws InterruptedException {
if(num==0){
//值为1的时候等待,值为0的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num--;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我-1完毕了
        this.notifyAll();
    }
}

这个问题也叫虚假唤醒因为在多线程中if只会判断一次,一般判断等待应该使用while循环判断,

如何解决虚假唤醒呢? 把if判断改为while循环判断即可 修改后代码如下

package com.wyh.pc;
/**
 * @program: JUC
 * @description: 线程通信
 * @author: 魏一鹤
 * @createDate: 2022-02-12 21:12
 **/
/**
  *线程之间通信问题:生产者和消费者  等待唤醒,通知唤醒
 * 线程交替执行 A B同时操作同一个变量
**/
//生产者和消费者代码编写思路:判断是否等待 进行业务处理 通知其他线程
//判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程
public class A {
public static void main(String[] args){
//线程操作资源类
        //创建资源类
        Data data=new Data();
//多线程处理 把资源抛给线程去执行 这里采用lambda表达式简化代码 ,第二个参数是现成名称
        new Thread(()->{
//从10循环+1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程A").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程B").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程C").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程D").start();
    }
}
//静态资源  独立耦合的 必须降低耦合性
class Data{
// 属性 数字变量
    private  int num=0;
//方法 数字+1 自增1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void increment() throws InterruptedException {
//为了防止虚假唤醒,应该用while进行判断等待
        while (num!=0){
//值为0的时候等待,值为1的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num++;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我+1完毕了
        this.notifyAll();
    }
//方法 数字-1 自减1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void decrement() throws InterruptedException {
        //为了防止虚假唤醒,应该用while进行判断等待
        while(num==0){
//值为1的时候等待,值为0的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num--;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我-1完毕了
        this.notifyAll();
    }
}

再次运行发现结果是正常的

传统的生产者消费者问题,防止虚假唤醒

线程A-->1

线程B-->0

线程A-->1

线程B-->0

线程A-->1

线程B-->0

线程A-->1

线程B-->0

线程A-->1

线程B-->0

线程A-->1

线程B-->0

线程C-->1

线程B-->0

线程A-->1

线程B-->0

线程C-->1

线程B-->0

线程A-->1

线程B-->0

线程C-->1

线程D-->0

线程C-->1

线程D-->0

线程A-->1

线程D-->0

线程C-->1

线程D-->0

线程A-->1

线程D-->0

线程C-->1

线程D-->0

线程C-->1

线程D-->0

线程C-->1

线程D-->0

线程C-->1

线程D-->0

线程C-->1

线程D-->0