今天学习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);
}
}
}
}