前言:
对于java的锁机制,可以说是长久以来一直困扰着我,经过长久的挣扎,终于决定来学习下,下面就就记录下日常学习的lock的笔记,以记录的形式来督促自己学习,毕竟有惰性,也不是学习天才,只能不断的鞭策自己,让自己学习。
说到java的锁,立刻就想到sychronized和Lock,但是让说一下java的锁,却又不知如何说起。。。
相比于Lock来说sychronized关键字不太好理解,我们先来看看Lock,这个是java写的我们作为一个码农,看代码应该更容易理解,就来看Lock的代码。首先上一段使用场景的代码
上图就主线程开了两个子线程,子线程run方法打印一句话,图1未使用lock,两个线程都打印了内容,而图2使用了lock,只有一个线程打印了内容,这有两个子线程都传入了lock这个实例化对象。这里我们就好理解这个锁了,就可以理解成,你只有先获取锁才能执行之后的操作,例如一个苹果两个人抢,谁抢到了谁就可以吃,吃就是之后的动作,苹果就是锁,是一个道理。
那么锁是如何实现的呢?来看看锁的实现原理,作为程序员,当然是以代码说话了
先看锁Lock的一个重要实现类ReentrantLock
一进入 ReentrantLock类就看到了三个内部类Sync、NonfairSync、FairSync,同步、非公平同步、公平同步,其实就是对应的我们公平锁和非公平锁的实现类,而Sync抽象类,将它们的共性进行抽取,形成了一个基类。
下来在来看看加锁的具体实现(Sync类)继承自AbstractQueuedSynchronizer类(抽象同步队列),AbstractQueuedSynchronizer又继承自AbstractOwnableSynchronizer
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
可以看下AbstractOwnableSynchronizer代码如下,就一个属性exclusiveOwnerThread独占线程,也就是说这个抽象类就是记录占有该锁的线程的。
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/** Use serial ID even though all fields transient. */
private static final long serialVersionUID = 3737899427754241961L;
/**
* Empty constructor for use by subclasses.
*/
protected AbstractOwnableSynchronizer() { }
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
/**
* Sets the thread that currently owns exclusive access.
* A {@code null} argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* {@code volatile} field accesses.
* @param thread the owner thread
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
/**
* Returns the thread last set by {@code setExclusiveOwnerThread},
* or {@code null} if never set. This method does not otherwise
* impose any synchronization or {@code volatile} field accesses.
* @return the owner thread
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
看到了记录占有锁的线程记录的类以及属性,那锁有没有被占用的标识在哪里呢?是在AbstractQueuedSynchronizer类中的state属性,类型为int。
搞清楚了两个重要标识,就来看看基础类sync类中的8个方法,一看便知如下。
看一个加锁的方法,非公平锁这个方法是怎么加锁的,还是来看源码
/**
* 非公平的尝试获取锁(因为一进来就直接先去加锁,而不是去队列等待,所以不公平)
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//判断锁的状态未被占用,则直接cas加锁(修改状态值)并设置独占线程拥有者为当前线程
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
}
看了这个类,可以知道锁的基本原理以及可重入的原理了,就是修改一个对象中的状态0为未锁定,其余大于0的状态为锁定状态,锁定后,并记录锁定的线程,当该线程再次来获取这个锁的时候,就对状态值+1.
看到了非公平锁的获取方法,再来看下公平锁的获取方法
// 公平的同步对象,公平锁使用的类
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 锁是未锁定状态时,先判断否有等待的队列,没有则cas加锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 设定占有锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
// 重入锁的处理(是重入,则状态值+1)
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
公平同步对象的中只有两个方法,一个是实现的基类Sync中的lock方法,调用了父类AbstractQueuedSynchronizer中的acquire方法,而acquire的方法中调用的tryAcquire方法就是上面的tryAcquire方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在lock中还有一个队列的概念,这就是下一个学习的目标,弄清楚这个队列的属性,以及怎么使用的。