Skip to content
DAILY QUOTE

“ ”

面试常考的问题:单例模式、排序算法、生产者和消费者、死锁

生产者和消费者问题 Synchronized 版

java
/**
 * 线程之间的通信问题:生产者和消费者问题!  等待唤醒,通知唤醒
 * 线程交替执行  A   B 操作同一个变量   num = 0
 * A num+1
 * B num-1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}
// 判断等待,业务,通知
class Data {
     // 数字 资源类
    private int number = 0;
    //+1
    public synchronized void increment() throws InterruptedException {
        /*
        假设 number此时等于1,即已经被生产了产品
        如果这里用的是if判断,如果此时A,C两个生产者线程争夺increment()方法执行权
        假设A拿到执行权,经过判断number!=0成立,则A.wait()开始等待(wait()会释放锁),然后C试图去执行
        生产方法,但依然判断number!=0成立,则C.wait()开始等待(wait()会释放锁)
        碰巧这时候消费者线程线程B/D去消费了一个产品,使number=0然后,B/D消费完后调用this.notifyAll();
        这时候2个等待中的生产者线程继续生产产品,而此时number++ 执行了2次
        同理,重复上述过程,生产者线程继续wait()等待,消费者调用this.notifyAll();
        然后生产者继续超前生产,最终导致‘产能过剩’,即number大于1
        if(number != 0){
            // 等待
            this.wait();
        }*/
        while (number != 0) {
     // 注意这里不可以用if 否则会出现虚假唤醒问题,解决方法将if换成while
            // 等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我+1完毕了
        this.notifyAll();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            // 等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我-1完毕了
        this.notifyAll();
    }
}

问题存在,A B C D 4 个线程! 虚假唤醒

首先到CHM 官方文档 java.lang包下 找到Object ,然后找到wait()方法:

因此上述代码中必须使用while判断,而不能使用if

java
public class A {
    public static void main(String[] args) {
        Date date = new Date();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    date.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    date.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    date.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    date.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"D").start();
    }
}

class Date { // 这是一个资源类

    private int number = 0;

    public synchronized  void increment() throws InterruptedException{
        while (number != 0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "--->" + number);
        this.notifyAll();
    }

    public synchronized  void decrement() throws InterruptedException {
        while (number == 0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "--->" + number);
        this.notifyAll();
    }

}

JUC版的生产者和消费者问题

官方文档中通过Lock 找到 Condition

点入Condition 查看

代码实现

java
/**
 * 线程之间的通信问题:生产者和消费者问题!  等待唤醒,通知唤醒
 * 线程交替执行  A   B 操作同一个变量   num = 0
 * A num+1
 * B num-1
 */
public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}
// 判断等待,业务,通知
class Data2 {
     // 数字 资源类
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //condition.await(); // 等待 
    //condition.signalAll(); // 唤醒全部
    //+1
    public  void increment() throws InterruptedException {
        lock.lock();
        try {
            // 业务代码
            while (number != 0) {
                // 等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            // 通知其他线程,我+1完毕了
            condition.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    //-1
    public  void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                // 等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            // 通知其他线程,我-1完毕了
            condition.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,是有其对旧技术的优势和补充!

Condition 精准的通知和唤醒线程

上述代码运行结果如图:

问题:ABCD线程 抢占执行的顺序是随机的,如果想让ABCD线程有序执行,该如何改进代码?

四个线程操作同一个对象,sychonized修饰的方法中,所有线程都在一个等待队列中,叫醒个人或者全体都会导致浪费系统资源,而使用condition制造三个等待队列,需要哪个线程就去唤醒哪个线程,因此conditon实现了精确唤醒

代码实现:

java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
 * 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();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1; 
    // number=1 A执行  number=2 B执行 number=3 C执行
    public void printA() {
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            while (number != 1) {
                // A等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>AAAAAAA");
            // 唤醒,唤醒指定的人,B
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB() {
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            while (number != 2) {
                // B等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>BBBBBBBBB");
            // 唤醒,唤醒指定的人,c
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC() {
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            // 业务,判断-> 执行-> 通知
            while (number != 3) {
                // C等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>CCCCC ");
            // 唤醒,唤醒指定的人,A
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

结果: