生产者消费者问题

多线程的问题可以拆分成四个部分,从这四个方面来考虑并发或者同步执行时是否会出现问题:

1,原子性:在进行关键操作时,比如进行增加删除操作时,代码块部分是否只能被一个线程访问

2,可见性:所有线程是否可以得到共享变量的最新值

3,有序性:已经上锁的代码块被线程访问时,未上锁的代码块查看共享变量是否会出现脏数据

4,线程之间的通信:所有线程是否有一套完整的唤醒与等待的机制,当需要同步的时候可以考虑进程间通信

因为Atomic类、AQS组件使用起来没有那么顺手,在遇到多线程题目时未必能反应过来,不如掌握两种简单的方法,在以后遇到问题时以不变应万变

第一种synchronized实现原子性,volatile保证共享变量可见,所有代码上锁不存在指令重排序问题,waitnotife实现通信

public class Main {

    final static Object lock = new Object();
    volatile static int MAX = 10;
    volatile static int con = 0;

    public static void main(String[] args) {

        for(int i = 0; i < 100; i++){
            new Consumer().start();
            new Producer().start();
        }

    }
}

public class Consumer extends Thread{

    @Override
    public void run() {
        //锁可以加在while之外,表示执行这些操作之前必须拿锁
        synchronized (Main.lock){
            //必须使用while
            while(Main.con == 0){
                try {
                    //等待前叫醒其他人
                    Main.lock.notifyAll();
                    Main.lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Main.con--;
            System.out.println("消费后" + Main.con);
            //结束时叫醒其他人
            Main.lock.notifyAll();
        }
    }
}

public class Producer extends Thread{

    @Override
    public void run() {
        synchronized (Main.lock){
            while(Main.con == Main.MAX){
                try {
                    Main.lock.notifyAll();
                    Main.lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Main.con++;
            System.out.println("生产后" + Main.con);
            Main.lock.notifyAll();
        }
    }
}

第二种使用lock锁,实现选择性通知;分成两个等待队列来获取同一把锁,使用signAll+await实现线程之间通信

public class Main {

    final static Lock lock = new ReentrantLock();
    volatile static int MAX = 10;
    volatile static int con = 0;
    static Condition producer = lock.newCondition();
    static Condition consumer = lock.newCondition();

    public static void main(String[] args) {

        for(int i = 0; i < 100; i++){
            new Consumer().start();
            new Producer().start();
        }

    }
}

public class Consumer extends Thread{

    @Override
    public void run() {
        Main.lock.lock();
        try {
            while(Main.con == 0){
                Main.producer.signalAll();
                Main.consumer.await();
            }
            Main.con--;
            System.out.println("消费后" + Main.con);
            Main.producer.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            Main.lock.unlock();
        }
    }

}

public class Producer extends Thread{

    @Override
    public void run() {
        Main.lock.lock();
        try {
            while(Main.con == Main.MAX){
                Main.consumer.signalAll();
                Main.producer.await();
            }
            Main.con++;
            System.out.println("生产后" + Main.con);
            Main.consumer.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            Main.lock.unlock();
        }
    }
}

在上面的代码中重点关注一个问题,虚假唤醒问题:wait 方法的特性是,在哪里睡觉,在哪里醒来,这导致如果使用 if 判断,会出现线程安全问题,因此需要使用 while 来循环判断

注意这里的 lock 在 try 的外面。如果放在里面,并且在获取锁时发生了异常,那么肯定也会走 finally 代码块,执行 lock.unlock 去释放锁,可问题是我还没获取到锁啊,因此会抛出如下异常
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值