ReentrantLock常常对比着synchronized来分析,我们先对比着来看然后再一点一点分析。
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。
ReentrantLock好像比synchronized关键字没好太多,我们再去看看synchronized所没有的,一个最主要的就是ReentrantLock还可以实现公平锁机制。什么叫公平锁呢?也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。
加锁的方式
ReentrantLock加锁是修改锁的状态值,state属性,当加锁时,加一即可,因为可重入,当当前线程第二次加锁时,就会直接加一。
final void lock() {
if (compareAndSetState(0, 1))//通过cas修改state的值
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
Synchronized加锁是每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
synchron就是抢占monitor,抢到就是抢到了锁。进入数加一,锁重入加一就行,释放一次减一,等进入数为0的时候锁就为无人占有。
当有线程再次申请这个锁的时候,不会直接去申请,而是会查看ACC_SYNCHRONIZED访问标识,查看是否被设置。而这个标记是存在对象的对象头中的Mark Word中,下面介绍一下Mark Word。
java对象的请求头:64位计算机 为8*12=96bit 前64位为Mark Word存储hashcode, GC分代信息,和锁的状态信息。
当对象刚创建时,前56位存储hashcode,后面4个存GC分带信息,三位存锁的状态。
无锁:001.偏向锁:101 偏向锁记录线程ID
当锁膨胀为轻量锁、重量锁时,前62位都为线程号后两位为状态号
00:轻量级 10重量级 11表示GC
ReentrantLock详解
构造方法
一个无参构造,默认为非false,非公平锁。
当传入new ReentrantLock(true)时会创建一个公平锁。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
在创建ReentrantLock时,可以传入true或false来确定是要创建公平锁还是非公平锁,默认是非公平锁(false)。
第一次加锁就是上面写的那样,当有第二个线程申请锁时,申请失败会加入到一个阻塞队列中。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/*通过tryAcquire尝试获取独占锁,如果成功返回true,失败返回false
如果tryAcquire失败,则会通过addWaiter方法将当前线程封装成Node添加到AQS队列尾部
acquireQueued,将Node作为参数,通过自旋去尝试获取锁*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {//如果锁没有人用,则直接获取
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return 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;
}
解锁,调aqs的release方法
public void unlock() {
sync.release(1);
}
//重写的AQS的方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//状态值减一
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}