所谓重入锁,即支持重入性,表示能够对共享资源重复加锁,即当前线程获取该锁再次获取不会被阻塞。
在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功; 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。
ReentrantLock分为公平锁与非公平锁,默认选择的是非公平锁(无参构造函数),另一种构造方式,可传入一个boolean值。true时为公平锁。
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//state为0表示该锁没有被任何线程占有,可以被获取
if (c == 0) {
//如果线程当前状态值accquires为0
if (compareAndSetState(0, acquires)) {
//设置当前拥有独占访问权限的线程。
setExclusiveOwnerThread(current);
return true;
}
}
//如果线程被占有,检查占有线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
//再次获取锁,计数加1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果被其他线程占有,则获取锁失败
return false;
}
每次重新获取锁都会对同步状态加1操作,那么释放锁相对的,就会进行减一操作,释放锁核心方法tryRelease
protected final boolean tryRelease(int releases) {
//同步状态减1
int c = getState() - releases;
//如果当前线程,不是锁占有线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//只有当同步状态c等于0,锁才能被成功释放。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//c不等于0,锁释放失败 返回false
setState(c);
return free;
}
每次锁重新获取都会对同步状态进行加一的操作,同样释放锁tryRelease执行减一操作,必须等到同步状态为0时,锁才算成功释放。也就是锁被获取了n次,只有释放n次才算成功释放。
ReentrantLock支持两种锁,公平锁与非公平锁。
ReentrantLock的构造函数可以传递一个boolean值,true时为公平锁,false为非公平锁。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
所谓公平锁,就是锁的获取顺序符合请求上的绝对时间顺序,满足FIFO(先入先出)。
ReentrantLock还有无参构造函数,为非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
上面讲了非公平锁的nonfairTryAcquire方法,这里看看公平锁的获取逻辑,tryAcquire方法。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
上述代码可以看出,与非公平锁的区别就在多了一个hasQueuedPredecessors()的判断。该方法是用来判断当前节点在队列中是否有前驱节点。如果有,说明有线程比当前线程更早的请求资源,根据先入先出原则,所以当前线程请求锁失败。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//当前队列不为空,当前节点不是头节点,当前节点的线程不是当前线程
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。
公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。
package com.lw.study.thread;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author:
* @Date: 16:46 2018/8/30
*/
public class LockDemo extends Thread{
private static ReentrantLock lock = new ReentrantLock();
private static int count = 0;
@Override
public void run() {
try {
lock.lock();
for (int i = 0; i < 5; i++) {
System.out.println("ThreadName : " + Thread.currentThread().getName() +" : " +count);
count ++;
}
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
LockDemo lockDemo = new LockDemo();
LockDemo lockDemo2 = new LockDemo();
lockDemo.start();
lockDemo2.start();
lockDemo.join();
lockDemo2.join();
System.out.println(count);
}
}
输出结果
hreadName : Thread-0 : 0
ThreadName : Thread-0 : 1
ThreadName : Thread-0 : 2
ThreadName : Thread-0 : 3
ThreadName : Thread-0 : 4
ThreadName : Thread-1 : 5
ThreadName : Thread-1 : 6
ThreadName : Thread-1 : 7
ThreadName : Thread-1 : 8
ThreadName : Thread-1 : 9
10
可以看到,在Thread-0执行完后,Thread-1才开始执行。如果不加锁,结果将是两个线程交替随机执行。
ThreadName : Thread-0 : 0
ThreadName : Thread-0 : 1
ThreadName : Thread-1 : 0
ThreadName : Thread-0 : 2
ThreadName : Thread-1 : 3
ThreadName : Thread-0 : 4
ThreadName : Thread-1 : 5
ThreadName : Thread-0 : 6
ThreadName : Thread-1 : 7
ThreadName : Thread-1 : 9
10