赞
踩
什么是ReentrantLock?
这是标准库给我们提供的另一种锁,前面我们学到的一种是synchronized
顾名思义,Reentrant:可重入的 但我们也知道,synchronized锁不也是可重入的嘛?
那这个锁到底有什么其他的过人之处呢?
传统加解锁:
synchroinzed加锁操作是基于代码块的方式加解锁的(在代码块内部时即为加锁状态,执行完代码块就释放锁,整个操作都是自动的)
reentrantLock加解锁状态就十分传统:
lock():加锁
unlock():解锁
这样的写法的好处就是可以让代码更灵活可变
若一段代码的临界区运行时间很长,如果使用synchronized,那么其他没有获取到锁对象的线程就会阻塞很长一段时间,但如果使用reentrantLock,即可以指定哪个时间段就可以释放锁对象,不会出现阻塞很长一段时间的场景,其加锁解锁完全由具体实际情况操作
但这样写的坏处为:非常害怕程序执行不到unlock方法,那么就无法解锁
比如程序在unlock之前抛出异常或者return时
解决方法:
将unlock放进finally代码块中:必会被执行到
可重入的:
public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); System.out.println("main线程获取锁对象"); try{ lock.lock(); Thread.sleep(100); testReentrant(); }finally { System.out.println("main线程释放锁"); lock.unlock(); } } public static void testReentrant(){ try{ lock.lock(); System.out.println("test方法获取锁对象"); }finally { System.out.println("test方法释放锁"); lock.unlock(); } }
可打断的:
lockInterruptibly()什么叫做可打断的?
我们知道,在synchronized锁中,没有竞争到锁资源的线程会进入阻塞队列中,直到锁对象被释放才会重新尝试获取锁,在这个阻塞等待锁资源的时间段里线程是不能被打断的,也就是你调入此线程的interrupt方法没有什么作用
在ReentrantLock中,调用lockInterruptibly方法后,此时锁具有可打断性,如果线程在阻塞等待时,有其他线程调入此线程的interrupt方法,此时线程会被唤醒,且抛出异常
先验证synchroinzed不可打断性
public static void main(String[] args) throws InterruptedException { // ReentrantLock的可打断性:在阻塞等待过程中 如果被其他线程调用了interrupt方法,就会唤醒并抛出异常 // 果有其他线程调用了该线程的interrupt()方法进行中断操作,那么该线程将被唤醒,并抛出InterruptedException异常 // //先验证synchronized锁的不可打断性 Object loker = new Object(); Thread t1 = new Thread(() -> { synchronized (loker) { System.out.println("t1线程获得锁,t1线程没有被打断"); } }); t1.start(); synchronized (loker) { System.out.println("主线程拿到锁"); //此时t1线程没有获取锁对象,处于阻塞队列中 System.out.println("打断t1线程"); //线程1并没有被唤醒抛出异常 t1.interrupt(); Thread.sleep(100); } }
lockInterruptibly():可打断性
public static void main(String[] args) throws InterruptedException { //验证reentrantLock锁的可打断性 ReentrantLock lock = new ReentrantLock(); Thread t = new Thread(() -> { for (int i = 0; i < 2; i++) { try { System.out.println("t线程尝试获取锁"); //锁的可打断性 lock.lockInterruptibly(); System.out.println("t线程拿到锁"); } catch (Exception e) { e.printStackTrace(); System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); System.out.println("线程被打断唤醒,没有获取锁对象,后续的如何操作由程序员代码决定"); //打印一个HelloWorld System.out.println("HelloWorld"); System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); } } try { lock.lock(); System.out.println("获取到锁对象"); } finally { System.out.println("t线程释放锁对象"); lock.unlock(); } }); System.out.println("主线程获取锁"); lock.lock(); Thread.sleep(100); //此时线程t处在阻塞队列 t.start(); System.out.println("主线程打断t1线程"); t.interrupt(); System.out.println("主线程释放锁"); lock.unlock(); }可以看到在主线程对t线程执行interrupt方法后,t线程被唤醒且抛出异常,其里边的内容依然继续执行
tryLock:
判断是否成功获取锁对象
public static void main(String[] args) { //tryLock 是否成功获取到锁 ReentrantLock lock = new ReentrantLock(); System.out.println(lock.tryLock()); // true 主线程获取锁 Thread t1 = new Thread(() -> { //此时主线程已经占用锁了 走else if(lock.tryLock()){ System.out.println("线程获取到锁对象"); }else{ System.out.println("立即返回"); return; } try{ lock.lock(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } }); t1.start(); }
设置最大等待时间
public static void main(String[] args) throws InterruptedException { //tryLock 是否成功获取到锁 ReentrantLock lock = new ReentrantLock(); System.out.println("主线程获取锁:"+lock.tryLock()); // true 主线程获取锁 Thread t1 = new Thread(() -> { //此时主线程已经占用锁了 走else try { //最多等待3s if(lock.tryLock(3, TimeUnit.SECONDS)){ System.out.println("线程获取到锁对象"); lock.lock(); }else{ System.out.println("t1线程立即返回"); return; } } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("线程释放锁资源"); lock.unlock(); } }); t1.start(); Thread.sleep(2000); //休眠2s后 主线程是否锁资源 lock.unlock(); }由于最大等待时间为3s,主线程休眠2s后就释放了锁资源,故线程可以拿到锁资源
公平锁:
需要在创建ReentrantLock锁对象时给出true参数-----fair
我们知道synchronized是非公平锁,线程抢占式调度,没有调度规则
而reentrantLock实现了公平锁,根据线程阻塞时间的长度来决定线程获取锁资源的优先级, 阻塞时间越长的线程优先级越高
public static void main(String[] args) throws InterruptedException { //公平锁 fair ReentrantLock lock = new ReentrantLock(true); //阻塞时间越长的优先级越高 Thread t1 = new Thread(() -> { try{ lock.lock(); System.out.println("t1获取锁对象"); }finally { System.out.println("t1释放锁对象"); lock.unlock(); } }); Thread t2 = new Thread(() -> { try{ lock.lock(); System.out.println("t2获取锁对象"); }finally { System.out.println("t2释放锁对象"); lock.unlock(); } }); Thread t3 = new Thread(() -> { try{ lock.lock(); System.out.println("t3获取锁对象"); }finally { System.out.println("t3释放锁对象"); lock.unlock(); } }); //主线程获取锁 System.out.println("主线程获取锁资源"); lock.lock(); //休眠1s后 开启t1线程 Thread.sleep(1000); t1.start(); //休眠2s后,开启t2线程 Thread.sleep(1000); t2.start(); //休眠3s后,开启t3线程 Thread.sleep(1000); t3.start(); System.out.println("主线程释放锁对象"); lock.unlock(); //按照逻辑 执行顺序应该是 t1 t2 t3 ----- 等待时间: t1 3s t2 2s t3 1s }。
。
公平锁的优势:保证所有线程都可以获取到锁资源,不会饿死在阻塞队列中(一直阻塞等待)
劣势:吞吐量降低
Condition:
synchroinze锁中,可以通过Object类的wait/notify方法来决定线程执行的先后顺序,在synchroinzed中,只有一个waitSet等待队列
而在ReentrantLock中,使用Condition类下的signal和await方法来实现等待/唤醒操作。
每一个Condition都是一个等待队列,所以在ReentrantLock下一个锁可以对应多个Condition,这也就说明了更加细分了其目的性:为什么要wait:为什么要notify
Condition对象可以通过ReentrantLock的newCondition方法来获取
await():
public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); //Condition Condition condition = lock.newCondition(); System.out.println("main获取到锁对象"); lock.lock(); System.out.println("执行await方法"); condition.await(); System.out.println("能够执行到这里吗?"); }可以看到主线程被阻塞,无法执行到await下方的代码。
signal():
public static void main(String[] args) throws InterruptedException { //await:使线程进入阻塞队列 释放锁资源 //signal:唤醒被await的线程 ps:并不会立马执行被唤醒的线程 而是将当前的任务执行完 类似于synchronized代码块中的notify后也是先把代码块中的内容执行完 ReentrantLock lock = new ReentrantLock(); //Condition Condition condition = lock.newCondition(); Thread t1 = new Thread(() -> { try{ lock.lock(); System.out.println("t1获取锁对象"); System.out.println("执行await方法 进入等待 释放锁资源"); condition.await(); System.out.println("t1线程被唤醒"); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("t1释放锁对象"); lock.unlock(); } }); t1.start(); Thread.sleep(4000); lock.lock(); //main执行signal方法 唤醒t1线程 锁资源 t1并不会立马执行 而是等主线程执行完 condition.signal(); System.out.println("main线程继续正常向下执行"); System.out.println("main线程释放锁资源"); lock.unlock(); }我们可以发现,在调用signal方法后,会唤醒线程,但并不会立马执行此线程,而是将当前的内容执行完并释放锁资源后,才会执行被唤醒的线程
awaitUninterruptibly():
await操作后不可被打断
先来看看没有加此方法 ---- 可被打断
public static void main(String[] args) throws InterruptedException { //awaitUninterruptibly() ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); Thread t1 = new Thread(() -> { lock.lock(); System.out.println("获取锁资源"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("线程被打断"); }finally { System.out.println("释放锁资源"); lock.unlock(); } }); t1.start(); Thread.sleep(100); System.out.println("主线程打断t1线程"); t1.interrupt(); }我们可以看到,线程被打断后提前唤醒,依然会执行任务中没有完成的内容,与上面类似
加了awaitUninterruptibly()方法:
public static void main(String[] args) throws InterruptedException { //awaitUninterruptibly() ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); Thread t1 = new Thread(() -> { lock.lock(); System.out.println("获取锁资源"); try { condition.awaitUninterruptibly(); } finally { System.out.println("释放锁资源"); lock.unlock(); } }); t1.start(); Thread.sleep(100); System.out.println("主线程打断t1线程"); t1.interrupt(); }此时线程即使被打断也没有抛出异常
awaitNanos(long nanosTimeout)
规定最大等待时间 超过此时间自动唤醒 单位:纳秒
public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); Thread t1 = new Thread(() -> { lock.lock(); try { //5s condition.awaitNanos(5000000000L); System.out.println("线程自动唤醒"); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("t1线程释放锁对象"); lock.unlock(); } }); t1.start(); Thread.sleep(5100); System.out.println("main线程获取锁对象"); lock.lock(); System.out.println(lock.isHeldByCurrentThread()); lock.unlock(); System.out.println("main线程释放锁资源"); }当线程wait的时间超过最大指定时间后,会被自动唤醒
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。