Java并发JUC(二)

72 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
本文主要讲解内容:并发中的生产者消费者问题;8种锁现象。

4.生产者消费者问题

Synchronized实现的生产者消费者问题:实现的效果就是A线程执行一次+1,B线程执行一次-1

class Data {
    private int number = 0;
    //+1 -1
    public synchronized void increment() throws InterruptedException {
        if (number != 0) {
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>"+ number);
        //通知其他线程,+1完毕了
        this.notify();
    }
    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>"+ number);
        //通知其他线程,-1完毕了
        this.notify();
    }
}

这部分代码有个问题:线程虚假唤醒(虚假唤醒就是在多线程执行过程中,线程间的通信未按照我们幻想的顺序唤醒,故出现数据不一致等不符合我们预期的结果。)

基于Synchronized实现的改成Lock实现。

  • 第一步:new ReentranLock(),在代码中lock.lock()加锁

  • 第二步:try...catch...finally捕获异常(代码业务逻辑写到try,异常捕获到catch,释放锁在finally中进行)

  • 第三步:替换synchronized中的wait和notify方法

    • 具体方法:需要lock.newCondition();造一个Condition对象,也就是lock里面的对象监视器
    • 使用Condition中的await方法代替wait方法让线程睡眠;使用signalAll方法替代notify唤醒线程。
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//+1 -1
public  void increment() throws InterruptedException {
    lock.lock();
    try {
        while (number != 0) {
            //等待
            condition.await();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>"+ number);
        //通知其他线程,+1完毕了
        condition.signalAll();
    }catch (Exception e) {
        e.printStackTrace();
    }finally {
        lock.unlock();
    }
​
}

【注意】:只是这样做会存在一个问题,无法做到我们的需求:A执行完后执行B再执行C再执行D。Synchorized无法实现这种需求。

可以用多个Condition对象来解决这个问题,如下:

condition1.await();第一个线程处于睡眠
condition2.signal();第二个线程被唤醒
class Data3 {
    private Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    int number = 1;//1A 2B 3C
​
    public void printA() {
        lock.lock();
        try {
            while (number != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() +"AAAA");
            //唤醒指定线程
            number = 2;
            condition2.signal();
        } catch (Exception e) {
           e.printStackTrace();
        } finally {
            lock.unlock();
        }
​
    }
    public void printB() {
        lock.lock();
        try {
            while (number != 2) {
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() +"BBBB");
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC() {
        lock.lock();
        try {
            while (number != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() +"CCCC");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

5. 8锁现象理解什么是锁

如何判断锁的是谁?知道什么锁,锁的谁?

sycchronized锁的对象是方法的调用者。 static锁的是Class。

标准情况:锁的是用一个对象(对象只有一个),谁先拿到谁就先执行。如果是两个phone对象的话也是互不影响的,毕竟是两把锁了。

image.png 加入了普通方法:相当于一个上锁了一个没上锁,那并不影响普通方法的执行

image.png

增加两个静态的同步方法,两个对象,但是静态static锁的是class,所以还是一把锁。

1个静态同步方法,一个普通同步方法,一个对象,是两把锁的情况。

其实只要弄清是几把锁,锁的谁,那就一目了然了。