本文已参与「新人创作礼」活动,一起开启掘金创作之路。
ReentrantLock
ReentrantLock 是基于 AQS 实现的,内部有公平锁和非公平锁两种实现,差别在于新来的线程是否比已经在同步队列中的等待线程更早获得锁。
- 使用 lock() 和 unLock() 方法来加锁解锁,但上锁后发生异常不解锁可能会产生死锁,finally控制块
- 等待可中断,使用lockInterruptibly() 方法上锁,等待的线程会立刻响应中断请求,详见下。
- 可分组唤醒线程,使用Condition(条件)类同时绑定多个对象,就可分组唤醒线程。
- 支持超时机制。
- 支持自旋锁
lockInterruptibly()和lock()区别:
lockInterruptibly() :上锁后,等待的线程接收到Thread.interrupt() 方法的中断请求时,会立刻抛出中断异常来立即响应中断。
lock() :上锁后,等待的线程接收到中断请求时,会设置中断状态为true并继续等待,直到线程获取到锁后才会根据中断标识处理中断,即selfInterrupt中断自己。
Object对象的wait() 方法、Tread类的join() 、sleep(long) 方法等阻塞的线程,会不断的轮询监听 interrupted 标志位,收到中断请求也会立刻抛出中断异常(InterruptedException)并清空中断状态,即使用isInterrupted()方法会获得false。
synchronized
Synchronized锁是非公平锁,要么随机唤醒一个线程要么唤醒全部线程。
- 修饰方法,锁住this实例,底层通过 ACC_SYNCHRONIZED 标志位实现同步。
- 修饰代码块,锁住指定实例,底层通过monitor对象来实现同步。
- 静态同步static synchronized,锁住该类的所有实例。
- 加锁前会清除工作内存,再从主内存读取变量,释放锁前则会将工作内存中的变量写回主内存。
可以保证并发三大特性:原子性(锁内单线程)、有序性(锁内单线程)、可见性(刷主存)
ACC_SYNCHRONIZED标志位
ACC_SYNCHRONIZED标志位表示该方法必须先获得该对象的锁才能执行,执行到这个方法时,就会先判断是否有标志位,如果有这个标志位,就会先尝试获取monitor对象,获取成功会才能执行方法,方法执行完成后再释放monitor。在方法执行期间,其他线程都无法获取同一个monitor。
归根结底还是对monitor对象的争夺,只是同步方法是一种隐式的方式来实现。
monitor对象
每个Java对象都绑定了一个(C++实现的)Monitor对象,synchronized基于进入和退出monitor对象实现加锁解锁,底层是通过MonitorEnter指令和MonitorExit指令来实现。
1、MonitorEnter指令,如果当前monitor的进入数为0,线程就会进入monitor并把进入数+1,该线程就是monitor对象的拥有者(owner)。
如果该线程已经是monitor的拥有者,又重新进入,就会把进入数再次+1,也就是可重入的。
2、MonitorExit指令,执行monitorexit的线程必须是monitor的拥有者,指令执行后,monitor的进入数减1,如果减1后进入数为0,则该线程会退出monitor,其他被阻塞的线程就可以尝试去获取monitor的所有权。
总的来说,synchronized的底层原理是通过monitor对象来完成的。
Synchronized和Lock比较
相同点:
(1)都是可重入锁。
(2)都保证了原子性,可见性和有序性。
不同点:
(1)ReentrantLock是Lock接口的实现类,是API层面的锁,功能更多:等待可中断、支持超时机制、可以分组唤醒;synchronized是关键字,是JVM级别的锁,没有上述功能;
(2)synchronized在JDK1.6引入了锁升级策略,ReentrantLock会直接挂起线程。
(3)synchronized中的锁是非公平的,ReentrantLock默认也是非公平的,也可实现公平锁。
(4)synchronized隐式获取锁和释放锁,ReentrantLock显示获取和释放锁.
简述AQS
aobing.blog.csdn.net/article/det…
AQS(AbstractQuenedSynchronizer)是抽象的队列式同步器,定义了一套多线程访问共享资源的流程,是构建锁或其他同步组件的基础框架,使用了模版方法模式。
内部有一个双向链表的同步队列和一个volatile修饰的state变量作为共享资源。
内部类ConditionObject的condition对象包含一个等待队列,若获得锁的线程需要等待某个条件时,会进入 condition 的等待队列,当 condition 条件满足时,线程会从等待队列重新进入同步队列进行获取锁的竞争。
独占锁获取过程:
线程请求锁时会使用CAS操作修改共享资源,如果线程修改共享资源失败,则将线程放到同步队列的队尾自旋,并判断前驱结点是否是队列首节点,是首节点则不断尝试获取同步状态,不是首节点则中断等待;如果获取成功就执行临界区代码,执行完后释放同步状态,并唤醒后继节点。
共享锁获取过程:
区别在于,独占锁同一时刻独占只有一个线程获取同步状态。共享锁在同一时刻可以有多个线程获取同步状态,获取锁成功时会判断后继结点是否是共享模式,会唤醒共享模式的后继结点。
AQS获取/释放独占锁方法
获取独占锁:调用 tryAcquire 方法,成功则返回true,失败则返回false。
释放独占锁:调用 tryRelease 方法,成功则返回true,失败则返回false。
AQS获取/释放共享锁方法
获取锁共享锁:调用 tryAcquireShared 方法,返回值不小于 0 表示能获取。
释放共享锁:调用tryReleaseShared方法。
涉及的设计模式
AQS采用了经典的模板方法模式,规定了加锁与解锁的流程,但不具体实现请求锁与释放锁的方法。
AQS的tryAcquire/tryRelease方法与tryAcquireShared/tryReleaseShared都是供子类实现的模版方法,但是并没有使用Abstract修饰,避免了一种模式下需要实现实现另一种模式的方法。