关于我从“MySQL锁学到OS生产者消费者”这件事

今天学习MySQL锁时,发现MySQL锁就是为了解决并发问题。

MySQL锁机制

按是否互斥分:共享锁,互斥锁
按锁粒度分:全局锁,表锁(表锁,MDL锁),行锁

学习操作系统同步互斥

接着就想了解一下Java并发控制。就去看了《Java并发编程实战》这本书,看完发现其实Java的并发编程层面和MySQL锁的并发控制有些相似之处。

想到操作系统也有同步互斥内容,就去看了学校的教材《操作系统概念》。发现OS的并发控制和Java的并发控制也挺像的,只是进程和线程的并发控制的区别而已。接着看到操作系统多进程的同步互斥,典型例子生产者消费者,于是敲起了代码,在Java层面实现了一下。

用Java实现生产者消费者

一开始的版本是这样的:

public class producercomsumer2 {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<Integer> list = new ArrayList<>();
        new MyProducer(5, list).start();
//        new MyProducer(5, list).start();
//        Thread.sleep(1000);
//        new MyConsumer(5, list).start();
//        new MyConsumer(5, list).start();
        new MyConsumer(5, list).start();
    }
}
class MyProducer extends Thread{
    int cap;
    List<Integer> list;
    public MyProducer(int cap, List<Integer> list){
        this.cap = cap;
        this.list = list;
    }
    public void produce(int i) throws InterruptedException {
        synchronized(list){
            if (list.size() == cap){ 
                System.out.println("producer在等待");
                list.wait();
            }
            list.add(i);
            System.out.println("生产一个:" + i);
            list.notify();
        }
    }
    @Override
    public void run() {
        for (int i = 0; i < 50; i ++ ){
            try {
                Thread.sleep(100);
                produce(i + 1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
class MyConsumer extends Thread{
    int cap;
    List<Integer> list;
    public MyConsumer(int cap, List<Integer> list){
        this.list = list;
        this.cap = cap;
    }
    public void consume() throws InterruptedException {
        synchronized(list){
            if (list.size() == 0){   //注意这里
                System.out.println("consumer在等待");
                list.wait();
            }
            System.out.println("消费一个" + list.get(0));
            list.remove(0);
            list.notify();  //注意这里
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i ++ ){
            try {
                Thread.sleep(100);
                consume();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

会发现主线程中,我只开启了一个生产者,一个消费者,运行后结果正确,如下:

...
...
消费一个48
生产一个:49
消费一个49
生产一个:50
消费一个50

遇到问题

但是当我设置了多个生产者消费者时(将main函数注释代码加上),结果就报了一个错误:

...
消费一个11
consumer在等待
Exception in thread "Thread-2" java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
	at java.base/java.util.Objects.checkIndex(Objects.java:359)
	at java.base/java.util.ArrayList.get(ArrayList.java:427)
	at producerconsumer2.MyConsumer.consume(producercomsumer2.java:60)
	at producerconsumer2.MyConsumer.run(producercomsumer2.java:71)
生产一个:11
消费一个11
...

是为什么呢?

在produce方法和consume方法中的if判断处,我加注释的地方:

synchronized(list){
          if (list.size() == 0){   //注意这里
              System.out.println("consumer在等待");
              list.wait();
          }
          System.out.println("消费一个" + list.get(0));
          list.remove(0);
          list.notify();  //注意这里
      }

当if条件满足时线程会调用wait(),即进入等待队列中排队获取锁。直到线程被唤醒时,就继续往下执行。

假设一种情况,有3个消费者:consumer1,consumer2,consumer3,两个生产者producer1,producer2,list为空,并且consumer1,consumer2进入了等待队列。

此时如果两个生产者一共生产了两个元素并notify()唤醒了consumer1,consumer2,那么list中就有两个元素。如果一个被consumer3消费,另一个被consumer1消费。那么当consumer2消费时就报异常了,因为list元素为空。这也就是上面的报错原因了。 解决方法就是用while循环。

最终代码:

public class producercomsumer2 {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<Integer> list = new ArrayList<>();
        new MyProducer(5, list).start();
        new MyProducer(5, list).start();
        Thread.sleep(1000);
        new MyConsumer(5, list).start();
        new MyConsumer(5, list).start();
        new MyConsumer(5, list).start();
    }
}
class MyProducer extends Thread{
    int cap;
    List<Integer> list;
    public MyProducer(int cap, List<Integer> list){
        this.cap = cap;
        this.list = list;
    }
    public void produce(int i) throws InterruptedException {
        synchronized(list){
            while (list.size() == cap){
                System.out.println("producer在等待");
                list.wait();
            }
            list.add(i);
            System.out.println("生产一个:" + i);
            list.notify();
        }
    }
    @Override
    public void run() {
        for (int i = 0; i < 50; i ++ ){
            try {
                Thread.sleep(100);
                produce(i + 1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
class MyConsumer extends Thread{
    int cap;
    List<Integer> list;
    public MyConsumer(int cap, List<Integer> list){
        this.list = list;
        this.cap = cap;
    }
    public void consume() throws InterruptedException {
        synchronized(list){
            while (list.size() == 0){
                System.out.println("consumer在等待");
                list.wait();
            }
            System.out.println("消费一个" + list.get(0));
            list.remove(0);
            list.notify();//
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i ++ ){
            try {
                Thread.sleep(100);
                consume();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值