锁是用来在多线程并发阶段保障数据同步的重要手段,防止出现脏数据,加锁代码在某个时间点只能由一个线程运行,其他线程等待。
普遍熟知的锁是synchronized关键字和Lock类。
一、synchronized关键字
这个在同步中是最常用的,分成对象锁和类锁,可以对方法和代码块进行加锁。
1、对象锁,锁住的是对象,用于
synchronized(this){}
synchronized(Object){}
2、类锁,锁住的是该类的所有实例
synchronized(Object.class){}
synchronized锁是在字节码级别上的锁,可以用javap(java自带的反编译工具)查看
例如查看这一段代码的字节码指令
javap执行结果如下:
可以看到是由monitorenter和monitorexit指令来实现加锁的,出现两次的monitorexit是为了确保解锁完成。
二、Lock
Lock是接口,有以下几个方法
1、获取锁
lock()
2、获取锁,除非线程中断
lockInterruptibly()
3、尝试获取锁,在锁可用获取锁
tryLock()
4、解锁
unlock()
5、返回该Lock的condition
newCondition()
Lock是需要配合Condition使用的,其有await和signal方法,类似于Object的wait和notify方法。
主要看一下Lock的实现类ReentrantLock。
(1)ReentrantLock默认是非公平锁(获取锁的顺序不是按照申请顺序来)
public ReentrantLock() {
sync = new NonfairSync();
}
也可以用参数指定是公平锁OR非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
FairSync和NonFairSync是继承Sync的,而Sync是继承自AbstractQueuedSynchronizer(简称AQS),AQS里面维护了锁的当前状态和线程等待队列。
(2)底层加锁方法
每个资源都有一个状态字段
private volatile int state;
1、非公平锁
final void lock() {
1、当资源状态为0的时候,更改为1
if (compareAndSetState(0, 1))
2、当前线程获取锁
setExclusiveOwnerThread(Thread.currentThread());
else
3、资源状态不为0的时候
acquire(1);
}
状态为0时,将拥有锁的线程设置为当前线程
void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
状态非0时,尝试获取锁
void acquire(int arg) {
if (!tryAcquire(arg) &&
//addWaiter是往线程等待队列中新增一个节点
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//中断当前线程
selfInterrupt();
}
tryAcquire方法指向的就是nonfairTryAcquire方法:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
1、获取锁状态
int c = getState();
if (c == 0) {
2、当资源状态为0的时候,更改为1,当前线程获取锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
3、当前线程为拥有锁的线程
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
4、修改锁状态
setState(nextc);
return true;
}
return false;
}
addWaiter方法是在AQS里面实现的,往线程等待队列尾部新增一个节点。
而acquireQueued方法作用是阻塞线程,重试获取锁,死循环,直至获取锁。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
1、获取前一个节点
final Node p = node.predecessor();
2、如果是头结点并且获取了锁
if (p == head && tryAcquire(arg)) {
3、将获取到锁的节点设置为头结点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
4、shouldParkAfterFailedAcquire是获取
锁失败后检查上一个节点的状态,判断是否
要阻塞当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
5、调用线程阻塞,判断是否已中断
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
parkAndCheckInterrupt方法:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
阻塞当前线程
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
可以看出,最终是调用Unsafe的park方法实现加锁的。
2、公平锁
公平锁和非公平锁的区别只有一个,就是在tryAcquire方法中多了一个hasQueuedPredecessors方法。
boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
1、获取锁状态
int c = getState();
if (c == 0) {
2、判断当前线程在等待队列中是否是第二个结点,
也就是等待时间最长的节点
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
3、当前线程已经拥有锁
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方法,判断当前线程是否是第二个节点,也就是等待时间最长的节点
boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
(3)释放锁
1、unlock方法
public void unlock() {
sync.release(1);
}
2、release方法
boolean release(int arg) {
1、释放锁,也就是修改锁状态
if (tryRelease(arg)) {
Node h = head;
2、释放完成后,唤醒下一个等待线程。
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
boolean tryRelease(int releases) {
1、获取剩余状态
int c = getState() - releases;
2、判断当前线程是否拥有锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
3、只有当锁状态为0时,才是完全释放
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
第3步是因为同一个线程多次调用lock时,锁状态都会相应的新增,所以可以从这里看出,如果多次调用Lock进行加锁,在解锁的时候也要多次调用unLock方法。
三、Synchronized和Lock的区别
(1)synchronized是关键字,Lock是类,类拥有更大的自由度。
(2)synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁。
(3)synchronized会自动释放锁,Lock需要执行unLock方法。
(4)synchronized是非公平的,Lock两者都可。
(5)Lock可以通过某个condition精确唤醒某个线程。
ok,以上就是synchronized和Lock的粗略分析。