Lock锁接口在JAVA SE5之后,出现在并发包中.它提供了与synchronized关键字一样的同步功能.只是在使用时需要显式地获取和释放锁,缺点就是缺少像synchronized那样隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
可重入锁和不可重入锁
- 可重入锁:一个线程调用一个加锁的方法后,还可以调用其他加同一把锁的方法.
- 不可重入锁:一个线程获取到一把锁之后,该线程是无法调用其他加了该锁的方法.这种情况,容易造成死锁.比如:方法一添加了不可重入锁,方法二添加了不可重入锁,并且调用了方法一.这种情况下,调用方法二就会出现死锁的情况.那么.重入锁就可以避免这种情况.
- 常用的可重入锁:
Sychronized,java.util.concurrent.locks.ReentrantLock
ReentrantLock基本使用
ReentrantLock对资源进行加锁,同一时刻只会有一个线程能够占有锁.当前锁被线程占有时,其他线程会进入挂起状态,直到该锁被释放,其他挂起的线程会被唤醒并开始新的竞争.
public class ReentrantLockExample {
private ReentrantLock reentrantLock = new ReentrantLock();
private void test(){
reentrantLock.lock();
System.out.println("进行原子操作");
try {
Thread.sleep(3000l);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
ReentrantLockExample reentrantLockExample = new ReentrantLockExample();
for (int i = 0; i < 5; i++) {
executorService.submit(new Thread(reentrantLockExample::test));
}
}
}
通过控制台的输出信息可知:每隔三秒输出一次信息.
ReentrantLock很大程度的依赖了抽象类AbstractQueuedSynchronizer,这里需要先对AbstractQueuedSynchronizer进行了解.看我下面的这篇文章
ReentrantLock又有公平锁和非公平锁之分,所以可以看到在源码中有两个锁的实现
公平锁:每个线程的资源竞争是公平的.按照自身线程调用lock方法的顺序来获取锁,即先到先得.
非公平锁:每个线程的资源竞争是顺序不定,谁的优先级高,那么谁就会先获得锁.
ReentrantLock几个重要方法的剖析
- 构造方法:ReentrantLock的无参构造器,默认将局部变量sync赋值为NonfairSycn.
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
- lock():调用sycn内部类的lock方法.
1.获取一把锁,如果该锁没有被其他线程持有,则立即返回,并将锁计数器加一;
2.如果当前线程已经持有该锁,则将锁计数器加一,并立即返回.
3.如果该锁被其他线程持有,那么当前线程的目的将会无效并进入休眠状态.直到锁计数器的值为1.
public void lock() {
sync.lock();
}
这里先看一下,公平锁的lock方法的实现.
FairSync:默认调用AbstractQueuedSynchronizer的acquire()方法.
final void lock() {
acquire(1);
}
AbstractQueuedSynchronizer:
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
非公平锁的lock方法实现:
NonfairSync:
final void lock() {
//通过CAS操作,将AQS中的stateOffset从0改为1.
if (compareAndSetState(0, 1))
//将当前线程设为独享线程
setExclusiveOwnerThread(Thread.currentThread());
else
//否则,再次请求同步状态.一般参数为0是释放锁,参数为1是获取锁
acquire(1);
}
AbstractQueuedSynchronizer:其中tryAcquire()是由具体的实现类实现的.
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这里分为两步进行分析:
1.NonfairSync:执行tryAcquire方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取当前AQS的状态
int c = getState();
if (c == 0) {//同步状态为0时,执行CAS操作
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}//当前线程已经获取到锁,因为当前是重入锁,则state+1,并返回true
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
2.NonfairSync:tryAcquire方法返回false之后,会执行acquireQueued方法.
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//尾节点不为空
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
因为NonfairSync是非公平锁,所以对于新来的线程和同步队列的线程,都能调用这个方法来获取到锁.
由于实现过于复杂,这里总结来说:当多个线程赖竞争锁时,只会有一个线程能够获取到锁,那么其他线程将会通过CAS操作(保证数据的一致性)来将当前线程添加到同步队列中,当所有线程进入到同步队列之后,就会进入自旋状态(死循环判断当前线程所在节点的前驱节点是否为head节点)去尝试获取同步状态.
- lockInterruptibly()或者tryLock()
可中断的获取方式.两者最终都会调用方法:
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
如果检测到线程的中断,将会直接抛出异常.