持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情
1、JUC显示锁
通常对于临界资源的加锁操作有两种方式:synchronized和huc的lock的方式;
- synchronized的是基于java内置锁的monitor监视器锁实现的,内部的加锁逻辑由jvm虚拟机来完成,使用简单方便,但是功能较为单一,而且并发量一大,就会升级成重量级锁,重量级锁存在线程的阻塞和唤醒操作造成线程的内核态和用户态的来回转换,非常影响性能;
- JDK5 版本引入了 Lock 接口,它纯用java语言实现,java代码中通过显示的调用,来满足日常开发需求,功能相对丰富,支持锁的中断唤醒、限时抢锁、公平非公平等
2、JUC锁的实现核心---------lock接口
lock接口提供了一系列抢释放锁逻辑
- lock() 抢锁方法,成功则继续执行,失败则阻塞
- lockInterruptibly():中断抢锁,当前线程在抢锁过程中可以响应中断线程
- tryLock():尝试抢锁,非阻塞,抢锁成功返回true失败则是false,通常结合cas算法;
- tryLock(long time, TimeUnit unit)限时抢锁
- unlock():释放锁
- newCondition();:获取与显示锁绑定的Condition对象,用于等待通知;
3、ReentrantLock可重入锁
ReentrantLock实现了Lock接口;
public class ReentrantLock implements Lock,
内部定义了两个类实现AQS
因此ReentrantLock具备锁住临界资源的能力,同时他还具备限时抢占、可中断抢占等一些 高级锁特性。
ReentrantLock属于独占锁
独占锁:简单地说就是在同一时刻,某一临界资源只能由一个线程获得,只有当这个线程释放掉这个资源的占有权,其他线程才能来抢锁操作;
跟synchronized一样,它也是可重入的
可重入的意思是,同一个资源可以由同一个线程多次获得,此时会有一个计数器来记录可重入数,直到计数器释放成0,才能让别的线程使用;
使用
Lock lock = new ReentrantLock ();
lock.lock(); //抢占锁
try {
//step2:抢锁成功,执行临界区代码
} finally {
lock.unlock(); //step3:释放锁
}
上述是简单的使用,值得注意的是在抢占锁与异常捕获中间 不要有其他代码。以免在次中间发生异常造成锁无法释放的情况;抢占锁操作 lock.lock( ) 必须在 try 语句块之外,因为lock方法并没有异常要处理,而且一旦说lock失败,也不需要去释放锁;
4、显示锁的线程间通信(condition)
java内置锁的通信方式为wait、notify,用于完成阻塞线程很唤醒线程,他们是基于java内置锁,要唤醒阻塞着的线程,必须是阻塞线程跟唤醒线程属于同一个对象锁;因为这两个方法通常在同步代码块中使用;
而lock的阻塞和唤醒跟内置锁很类似:await() signal(); signalAll();
功能和java内置锁类似
Condition 对象是基于显式锁的,所以需要借助于显式锁实例去获取其绑定的 Condition 对象。不过,每一个 Lock 显式锁实例可以有任意数量的 Condition 对象。具体来说,可以通过 lock.newCondition()方法,去获取一个与当前显式锁绑定的 Condition 实例,然后通过该 Condition 实例即可进行“等待-通知”方式的线程间通信。
5、LockSupport类
LockSupport 是 JUC 提供的一线程阻塞与唤醒的工具类,该工具类可以让线程在任意位置阻 塞和唤醒,其所有的方法都是静态方法。
常见方法:
| park() | 阻塞当前线程 |
|---|---|
| unpark(Thread thread); | 唤醒某个线程 |
| parkNanos(long nanos) | 带超时时间阻塞线程 |
| parkUntil(long deadline) | 带时间限制的阻塞 |
LockSupport.park()和 Thread.sleep()的区别 以及与 Object.wait( )的区别
- Thread.sleep()没法从外部唤醒,只能自己醒过来;而被 LockSupport.park( )方法阻塞的 线程可以通过调用 LockSupport.unpark()方法去唤醒。
- Thread.sleep( )方法声明了 InterruptedException 中断异常,;而使用 LockSupport.park( )方法时,不需要捕获中断异常。
- LockSupport.park( )方法、Thread.sleep( ) 方法所阻塞 的线 程,当被阻 塞线程 的 Thread.interrupt()方法被时,被阻塞线程都会响应线程的中断信号,唤醒线程的执行。不同的是, 二者对中断信号的响应的方式不同。LockSupport.park( )方法不会抛出 InterruptedException 异常, 仅仅设置了线程的中断标志;而 Thread.sleep( )方法还会抛出 InterruptedException 异常。
- LockSupport.park( )能更精准、更加灵活的阻塞、唤醒指定 线程。
- Thread.sleep( )本身就是一个 Native 方法;LockSupport.park( )并不是一个 Native 方法, 只是调用了一个 Unsafe 类的 Native 方法(名字也叫 park)去实现。
- LockSupport.park( )方法还允许设置一个 Blocker 对象,主要用来给监视工具或诊断工具 确定线程受阻塞的原因。
- Object.wait()方法需要在 synchronized 块中执行;而 LockSupport.park()可以在任意地方 执行。
- 当被阻塞线程被中断时,Object.wait()方法抛出了中断异常,调用者需要捕获或者再抛 出;当被阻塞线程被中断时,LockSupport.park()不会抛出异常,使用时不需要处理中断异常。