Java多线程并发实例生产者消费者--加锁实现

在这个案例中使用可重入锁来实现生产者消费模型。

在这个案例中分别使用两个线程对同一个对象进行操作,实现生产一个商品消费一个商品的操作。
多线程操作的三个重要步骤:

1. 线程操作资源类
2. 判断、干活(业务处理)、唤醒通知
3. 严防虚假唤醒

在资源类UserShareData中义了两个方法
producer() 方法用于生产一个商品
consumer()方法用于消费一个商品

在producer() 方法遵循多线程处理的3个步骤,因为使用的加锁机制,所以基本模式是这样:

加锁

try{
   1.
   循环判断{
      await() //使当前线程加入 await() 等待队列中,并释放当锁
   }

   2.具体的业务操作
   
   3.唤醒通知

}catch(InterruptedException e){
   异常处理
}finally{
   释放锁
}


1.判断
首先判断number是否等于0,如果number !=0 说明已经生产了商品,此时应该阻塞生产操作,在多线程操作中,判断操作必须是在循环中进行,否则会引发虚假唤醒
在循环判断内部使用condition.await(),功能就是生产者线程加入 await() 等待队列中,并释放当锁,当其他线程调用signal()会重新请求锁。与Object.wait()类似。

2.干活
就是进行具体的业务处理,在这个案例中,使得number+1操作来进行模拟

3.唤醒通知
使用condition.signalAll();来唤醒await()等待队列中的所有线程,与Object.notifyAll()功能类似

consumer()是消费商品操作,与producer() 方法的模式一致,只是number-1操作,故不再赘述。

class UserShareData{
    private int number = 0;
    private Lock lock = new ReentrantLock();

    // 一把锁,一体两面
    private Condition condition = lock.newCondition();

    // 生产商品
    public void producer(){
        lock.lock();
        try{
            // 1.判断 如果number !=0 说明已经有商品了,应该阻塞生产操作,否则就允许生产
            while(number != 0){
                condition.await();
            }

            // 2.干活 用增加数量模拟生产了商品
            number +=1;
            System.out.println(Thread.currentThread().getName()+"\t 生产的商品数量:"+ number);
            // 3.通知唤醒
            condition.signalAll();

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

    public void consumer(){
        lock.lock();
        try{
            // 1.判断 如果number ==0 说明已经没有商品了,应该阻塞消费操作,否则就允许消费
            while(number == 0){
                condition.await();
            }

            // 2.干活 用减少数量模拟消费了商品
            number -=1;
            System.out.println(Thread.currentThread().getName()+"\t 消费的商品数量:"+ number);

            // 3.通知唤醒
            condition.signalAll();

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

    }

}

测试类:

public class ProducerConsumerDemo_v2 {
    public static void main(String[] args) {
        UserShareData data = new UserShareData();
        new Thread( ()->{
            for (int i = 1; i < 10; i++) {
                data.producer();
            }
        } ,"生产者").start();

        new Thread( ()->{
            for (int i = 1; i < 10; i++) {
                data.consumer();
            }
        } ,"消费者").start();

    }
}

运行结果:

生产者	 生产的商品数量:1
消费者	 消费的商品数量:0
生产者	 生产的商品数量:1
消费者	 消费的商品数量:0
生产者	 生产的商品数量:1
消费者	 消费的商品数量:0
生产者	 生产的商品数量:1
消费者	 消费的商品数量:0
生产者	 生产的商品数量:1
消费者	 消费的商品数量:0
生产者	 生产的商品数量:1
消费者	 消费的商品数量:0
生产者	 生产的商品数量:1
消费者	 消费的商品数量:0
生产者	 生产的商品数量:1
消费者	 消费的商品数量:0
生产者	 生产的商品数量:1
消费者	 消费的商品数量:0

可以看到有10对生产者 + 消费者的提示。
在资源类的producer()和consumer()中都使用到了循环判断,为何会如此呢,看看jdk1.8原版中的描述:

As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop: 

synchronized (obj) {
 while (<condition does not hold>)
	 obj.wait();
 ... // Perform action appropriate to condition
}

可是试着吧这俩方法中的while修改成if,然后使用2个生产者线程和2个消费者线程,得到的某次允运行结果如下:
 

生产者1	 生产的商品数量:1
消费者1	 消费的商品数量:0
生产者1	 生产的商品数量:1
消费者2	 消费的商品数量:0
消费者1	 消费的商品数量:-1
消费者1	 消费的商品数量:-2
消费者1	 消费的商品数量:-3
消费者1	 消费的商品数量:-4
消费者1	 消费的商品数量:-5
消费者1	 消费的商品数量:-6
消费者1	 消费的商品数量:-7
消费者1	 消费的商品数量:-8
生产者1	 生产的商品数量:-7
消费者2	 消费的商品数量:-8
消费者2	 消费的商品数量:-9
消费者2	 消费的商品数量:-10
消费者2	 消费的商品数量:-11
消费者2	 消费的商品数量:-12
消费者2	 消费的商品数量:-13
消费者2	 消费的商品数量:-14
消费者2	 消费的商品数量:-15
生产者2	 生产的商品数量:-14
生产者1	 生产的商品数量:-13
生产者2	 生产的商品数量:-12
生产者1	 生产的商品数量:-11
生产者2	 生产的商品数量:-10
生产者1	 生产的商品数量:-9
生产者2	 生产的商品数量:-8
生产者1	 生产的商品数量:-7
生产者2	 生产的商品数量:-6
生产者1	 生产的商品数量:-5
生产者2	 生产的商品数量:-4
生产者1	 生产的商品数量:-3
生产者2	 生产的商品数量:-2

这个问题的根源就是在于产生了虚假唤醒,当恢复使用while判断之后测试正常,因此在线程中判断条件时必须使用循环判断。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值