JUC学习笔记(虚假唤醒问题、JUC的生产者和消费者关系)

309 阅读5分钟

虚假唤醒问题

synchronized下的生产者、消费者模式场景

  • 资源类的属性num 为 0,方法是 对num 加1 和 减1
  • 初始是两个线程进行wait和notifyAll通信,保证num为0时候+1,为1时-1
  • 线程A是对num进行10次的+1,线程B是对num进行10次的-1
  • 线程A执行+1的条件是if(num == 0) ,否则wait等待,执行完 +1 就 notifyAll 唤醒其他线程(此时只有B线程);B线程同理
  • 结果可以发现两个线程间的 线程间通信 正常 可以使逻辑结果正常
public class Demo {
    public static void main(String[] args) {

        Data data = new Data();

        // 线程A,进行加1
        new Thread(()->{
            try {
                // 10 次操作
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程A").start();

        // 线程B,进行减1
        new Thread(()->{
            try {

                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程B").start();

    }
}


/**
 * 待操作的Data资源类,需要解耦
 */

class Data{
    private int num = 0;

    public synchronized void increment() throws InterruptedException {
        if(num != 0){
            // 等待
            this.wait();
        }
        System.out.println(Thread.currentThread().getName() + "=> " + num);
        num ++;
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        if(num == 0){
            // 等待
            this.wait();
        }
        System.out.println(Thread.currentThread().getName() + "=> " + num);
        num --;

        this.notifyAll();
    }


}

输出:
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1

现在是A一个线程做 + 1,B一个线程做 - 1,如果是多个线程呢?

public class Demo {
    public static void main(String[] args) {

        Data data = new Data();

        // 线程A,进行加1
        new Thread(()->{
            try {
                // 10 次操作
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程A").start();

        // 线程B,进行减1
        new Thread(()->{
            try {

                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程B").start();

        // 线程A,进行加1
        new Thread(()->{
            try {
                // 10 次操作
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程C").start();

        // 线程B,进行减1
        new Thread(()->{
            try {

                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程D").start();

    }
}


/**
 * 待操作的Data资源类,需要解耦
 */

class Data{
    private int num = 0;

    public synchronized void increment() throws InterruptedException {
        if(num != 0){
            // 等待
            this.wait();
        }
        System.out.println(Thread.currentThread().getName() + "=> " + num);
        num ++;
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        if(num == 0){
            // 等待
            this.wait();
        }
        System.out.println(Thread.currentThread().getName() + "=> " + num);
        num --;

        this.notifyAll();
    }


}


输出:
线程A=> 0
线程D=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程D=> 0
线程D=> -1
线程D=> -2
线程D=> -3
线程D=> -4
线程D=> -5
线程D=> -6
线程D=> -7
线程D=> -8
线程A=> -9

虚假唤醒问题分析

image.png 解决办法就是将 if 判断 换成 while 即可,避免虚假唤醒问题

JUC下的生产者、消费者关系

Lock下的线程通信

  • Condition接口取代了Object监视器方法(wait、notify、notifyAll)使用自己的方法完成线程间通信
  • 使用lock对象的newCondition()方法可以获得condition对象,该对象有一个await()方法和singal()方法
  • 同时Condition支持精准的通知、唤醒线程(使线程按照顺序执行)
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {
    public static void main(String[] args) {

        Data data = new Data();

        // 线程A,进行加1
        new Thread(()->{
            try {
                // 10 次操作
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        },"线程A").start();

        // 线程B,进行减1
        new Thread(()->{
            try {

                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        },"线程B").start();

        // 线程A,进行加1
        new Thread(()->{
            try {
                // 10 次操作
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        },"线程C").start();

        // 线程B,进行减1
        new Thread(()->{
            try {

                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        },"线程D").start();

    }
}


/**
 * 待操作的Data资源类,需要解耦
 */

class Data{
    private int num = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public  void increment()  {

        // 加锁
        lock.lock();

        try {
            while(num != 0){
                // 等待
                condition.await();


            }
            System.out.println(Thread.currentThread().getName() + "=> " + num);
            num ++;
            // 唤醒
            condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 解锁
            lock.unlock();
        }


    }

    public  void decrement()  {
        // 加锁
        lock.lock();

        try {
            while(num == 0){
                // 等待
               condition.await();
            }
            System.out.println(Thread.currentThread().getName() + "=> " + num);
            num --;
            // 唤醒
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 解锁
            lock.unlock();
        }


    }


}
输出:
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1


Condition控制线程间精准通信

  • 场景如三个线程顺序执行
    • A线程先执行,使达到B线程的执行条件,唤醒B线程,A线程处于等待状态;
    • B线程接着执行,使达到C线程的执行条件,唤醒C线程,B线程处于等待状态;
    • C线程接着执行,使达到A线程的执行条件,唤醒A线程,C线程处于等待状态;
  • 实现原理是condition对象可以设置多个,每个对象可以和线程绑定一块。一个condition对象服务一个线程,因为加锁了,所以其他线程正在等待,而有执行条件的线程会接到通知从 等待--->执行--->更新num状态---->等待
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {

    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.A();
            }
        },"1线程").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.B();
            }
        },"2线程").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.C();
            }
        },"3线程").start();

    }
}

class Data {
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1;

    public void A(){
        lock.lock();

        try {
            while(num != 1){
                // 等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=> A 方法" );
            num  = 2;

            // 唤醒执行方法B的线程
            condition2.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


    }
    public void B(){
        lock.lock();

        try {
            while(num != 2){
                // 等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=> B 方法" );
            num  = 3;

            // 唤醒执行方法B的线程
            condition3.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


    }
    public void C(){
        lock.lock();

        try {
            while(num != 3){
                // 等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=> C 方法" );
            num  = 1;

            // 唤醒执行方法B的线程
            condition1.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


    }

}

输出:

1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法