0%

JUC-Lock(jdk1.8)

前言

Lock是JUC包下定义了锁相关方法的接口,相对于synchronized,其提供了更多的锁功能,如响应中断,超时锁等。
本章就基于Lock及Condition的使用和实现原理做一个学习总结。

Lock与synchronized

synchronized

  1. 基于JVM实现的非同步锁,锁释放由虚拟机完成,死锁可能性较低
  2. 使用wait/notify控制线程的等待和唤醒
  3. 提供锁升级方案提升性能
  4. 无法提供响应中断,超时锁等功能

Lock

  1. 利用CAS和代码逻辑实现
  2. 提供了synchronized没有的很多功能
  3. 锁的控制全由开发者自己操作,容易因没有释放锁而导致死锁

四种获取锁的方法

  1. void lock()
    lock()方法是最常用的获取锁的方法,使用该方法会一直尝试获取锁,直到获取成功。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public void testLock() throws InterruptedException {
    Lock lock = new ReentrantLock();
    lock.lock();
    threadPoolExecutor.execute(()->{
    logger.info("尝试获取锁");
    lock.lock();
    logger.info("获取到锁");
    lock.unlock();
    });
    Thread.sleep(5000);
    logger.info("过去了5S");
    lock.unlock();
    }
  2. void lockInterruptibly() throws InterruptedException
    响应线程中断,当线程中断时,会停止获取锁的尝试并抛出中断异常,如果线程没有发生中断则和lock()方法一样。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public void testLock() throws InterruptedException {
    Lock lock = new ReentrantLock();
    lock.lock();
    Thread thread = new Thread(() -> {
    logger.info("尝试获取锁···");
    try {
    lock.lockInterruptibly();
    } catch (InterruptedException e) {
    logger.info("中断");
    }
    lock.unlock();
    });
    thread.start();
    Thread.sleep(5000);
    logger.info("过去了5S");
    thread.interrupt();
    lock.unlock();
    }
  3. boolean tryLock()
    tryLock()lock()不一样,他只会尝试一次,如果成功则占用锁,失败则不再尝试。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public void testLock() throws InterruptedException {
    Lock lock = new ReentrantLock();
    lock.lock();
    threadPoolExecutor.execute(() -> {
    logger.info("1尝试获取锁···");
    boolean result = lock.tryLock();
    logger.info("1获取结果:{}", result);
    if (result) {
    logger.info("1获取成功");
    lock.unlock();
    }
    });
    threadPoolExecutor.execute(() -> {
    try {
    Thread.sleep(5000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    logger.info("2尝试获取锁···");
    boolean result = lock.tryLock();
    logger.info("2获取结果:{}", result);
    if (result) {
    logger.info("2获取成功");
    lock.unlock();
    }
    });
    Thread.sleep(5000);
    logger.info("过去了5S");
    lock.unlock();
    }
  4. boolean tryLock(long time, TimeUnit unit) throws InterruptedException
    本方法在tryLock()的基础上设置了一个等待时间,在限定时间内会一直等待锁释放,超过了就不再尝试获取锁。另外,本方法也是和lockInterruptibly()一样响应线程中断的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public void testLock() throws InterruptedException {
    Lock lock = new ReentrantLock();
    lock.lock();
    Thread thread1 = new Thread(() -> {
    logger.info("1尝试获取锁···");
    boolean result = false;
    try {
    result = lock.tryLock(6, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    logger.info("1获取结果:{}", result);
    if (result) {
    logger.info("1获取成功");
    lock.unlock();
    }
    });
    Thread thread2 = new Thread(() -> {
    logger.info("2尝试获取锁···");
    boolean result = false;
    try {
    result = lock.tryLock(7, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    logger.info("2获取结果:{}", result);
    if (result) {
    logger.info("2获取成功");
    lock.unlock();
    }
    });
    thread1.start();
    thread2.start();
    Thread.sleep(5000);
    thread1.interrupt();
    logger.info("过去了5S");
    lock.unlock();
    }

Condition

正如synchronized能够通过wait/notify控制线程等待/唤醒一样,Lock也有配套的Condition配合控制线程。
当抢占到lock锁资源时,便可使用lock中的Condition的方法控制线程。就像进入synchronized同步块后便可使用wait/notify方法一样。

方法介绍

  1. void await()
    最常用的等待方法,线程会一直等待直到有另一个线程调用signal()唤醒他,如果线程中断则抛出异常。

  2. void awaitUninterruptibly()
    与await()相似,不同的是当检查到当前线程中断,会进入一个安全的中断方法而不是抛出异常。

  3. long awaitNanos(long nanosTimeout) throws InterruptedException
    与await()的不同是会设定一个等待时间,只在这个时间段内等待。

  4. boolean awaitUntil(Date deadline) throws InterruptedException
    与awaitNanos(long n)类似,不过其设定的是一个时间点。

  5. void signal()
    唤醒方法,唤醒队列的头节点

  6. void signalAll()
    唤醒方法,唤醒队列中所有节点

Condition使用

  1. 线程等待唤醒

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public void testCondition() throws InterruptedException {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    threadPoolExecutor.execute(()->{
    logger.info("尝试获取锁···");
    lock.lock();
    logger.info("获取成功");
    try {
    condition.await();
    logger.info("苏醒");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    lock.unlock();
    });
    Thread.sleep(5000);
    logger.info("过了5s···");
    lock.lock();
    logger.info("master获取成功");
    condition.signal();
    logger.info("唤醒等待线程···");
    lock.unlock();
    Thread.sleep(5000);
    }
  2. 多个Condition

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public void testCondition() throws InterruptedException {
    Lock lock = new ReentrantLock();
    Condition conditionOne = lock.newCondition();
    Condition conditionTwo = lock.newCondition();
    threadPoolExecutor.execute(() -> {
    logger.info("1尝试获取锁···");
    lock.lock();
    logger.info("1获取成功");
    try {
    conditionOne.await();
    logger.info("1苏醒");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    lock.unlock();
    });
    threadPoolExecutor.execute(() -> {
    logger.info("2尝试获取锁···");
    lock.lock();
    logger.info("2获取成功");
    try {
    conditionTwo.await();
    logger.info("2苏醒");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    lock.unlock();
    });
    Thread.sleep(5000);
    logger.info("过了5s···");
    lock.lock();
    logger.info("master获取成功");
    conditionOne.signal();
    logger.info("唤醒等待线程1···");
    lock.unlock();
    //---------------------------------
    Thread.sleep(5000);
    logger.info("又过了5s···");
    lock.lock();
    logger.info("master获取成功");
    conditionTwo.signal();
    logger.info("唤醒等待线程2···");
    lock.unlock();
    Thread.sleep(5000);
    }

    可以看到,相对synchronized和wait,Lock和Condition的组合显然更加灵活和强大。
    一个Lock可以通过newCondition()同时拥有多个等待队列,互不干涉影响。阻塞队列正是利用这种方式实现放入/弹出的控制。