JAVA-DIY-8-WEEK

Persistence is not necessarily successful, but not sure will not succeed.

Posted by Hyuga on April 21, 2019

第8次讨论主题:锁

拓展

Java中的锁是什么?

Java世界里,一切皆对象。而每个对象都内置了一个锁,称之为对象锁,或者内部锁。这是Java的隐式锁,通过Synchronized来完成相关的锁操作。

因为隐式锁的实现有缺陷以及并发场景的复杂性,JDK1.5版本开始,又提供了基于java.util.concurrent.locks.Lock派生出来的显式锁

至此,两大锁门派介绍完毕。

锁的作用?

一个简单的方法调用,不包含共享数据,单线程操作,不需要要加锁。

而如果方法内部操作了共享数据,则需要加锁。

因为cpu时间片分配的原因,每个线程抢得部分时间片(不连贯)执行,抢得的时间片耗完后,则由其它抢到时间片的线程执行。

比如 i = 0;是共享数据,i = i + 1; 对应的cpu执行有三步,读i,i+1,赋值i。当线程执行完第二步后,线程cpu时间片耗完,这时另一个线程也执行了这段代码,那么i = 1;然后第二个线程cpu时间片耗完,线程1又抢到了cpu时间片,接着执行,i值为变为了1.

CPU时间片相关文章请自行Google。

锁应用于多线程下对共享数据操作的场景。

锁有哪些,分别用来干嘛?

公平锁、非公平锁
  • 公平锁:指多个线程按锁的申请顺序来获取锁。
    • 优点:等待的线程不会被饿死,按序就能拿到锁。
    • 缺点:整体吞吐量和效率很低。
  • 非公平锁:指多个线程不按锁的申请顺序来获取锁。
    • 优点:抢占式获取锁,整体吞吐量和效率都很高。
    • 缺点:可能有些线程会一直获取不到锁,处于等待状态,直至饿死。

公平锁:new ReentrantLock(true)

非公平锁:synchronized、new ReentrantLock()和new ReentrantLock(false)


可重入锁、不可重入锁
  • 可重入锁:也叫递归锁,同一个线程可以多次获得同一个锁,锁计数+1.
  • 不可重入锁:指同一个线程每次执行同步代码段的时间,必须获取锁后才能执行。

可重入锁:一定程度上能避免死锁。同个线程可递归获取同一个锁,锁计数+1.待层层解锁后,锁计数为0,则锁被释放。

不可重入锁:一个线程获取锁后,必须执行完释放了该锁,才能重新获取新的锁来调用该同步代码段。


独享锁、共享锁
  • 独享锁:这个很好理解,指这个锁一次只能被一个线程持有。
  • 共享锁:指这个锁可以被多个线程所持有。

互斥锁、读写锁
  • 互斥锁:同独享锁是相近的概念,也是这个锁一次只能被一个线程持有。
  • 读写锁:同共享锁是相近的概念,这个锁一次只能有一个写线程修改共享数据,但允许多个读线程读取共享数据。

互斥锁:ReentrantLock、synchronized

读写锁:通过ReentrantReadWriteLock实现。 读锁:ReentrantReadWriteLock.ReadLock 写锁:ReentrantReadWriteLock.WriteLock


乐观锁、悲观锁
  • 乐观锁:无锁编程模式,乐观地认为对共享数据的并发操作,是不会发生修改的。但是会在操作提交时检查数据的完整性。
  • 悲观锁:加锁编程模式,悲观地认为对共享数据的并发操作,一定会发生修改的,所以必须要加锁。

乐观锁:Java中常用的乐观锁方式有版本号、时间戳等。

悲观锁:比如mysql中的for update.


自旋锁
  • 线程获取不到锁的情况下不会立即阻塞,而是循环去尝试获取锁,能有效减少上下文切换的资源消耗,但也加大了CPU的消耗。

分段锁
  • 细化锁的粒度,比如把一个存储结构分为16等份,计算出将要存储的元素应该存在哪一段,对这一段空间进行加锁。

偏向锁/轻量级锁/重量级锁
  • 偏向锁:对于一段同步代码来说,锁偏向于第一次获取它的线程,如果继续执行的过程中,锁没有被其它线程持有,则持有偏向锁的线程将不需要同步,自动获取锁。
  • 轻量级锁:指当偏向锁被另一个线程所持有时,偏向锁就会自动升级为轻量级锁,其他线程会通过自旋的形式尝试索取锁,不会阻塞,提高性能。
  • 重量级锁:指当锁是轻量级锁时,另一个线程虽然是自旋,但自旋并不会一直持续,自旋一定次数后,如果还没有获取到锁,则线程会进入阻塞状态,导致锁膨胀为重量级锁。

偏向、轻量、重量,这三个锁都是针对于synchronized的三种不同阶段。


死锁
  • 多线程通信中,线程间都想获得对方的锁,因此而阻塞导致死锁的产生。

经典的ReentrantLock有哪三行的代码非常变态,谈谈如何改进。

能力有限,没能找出最渣的,只能找出一些自己觉得不太好的代码段。

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;
}
  1. c这种变量命名很不友好,命名不规范,review两行泪
  2. if (c == 0) {…} else if (current == getExclusiveOwnerThread()) {…}这段代码体验很不好,上下文逻辑看起来很费劲。可以用switch或者更好的表达方式去改写
  3. if或者else不加大括号
  4. !hasQueuedPredecessors() && compareAndSetState(0, acquires)复杂表达式应该抽取,用友好的变量名标注

优化

protected final boolean tryAcquire(int acquires) {
 if (canNotAcquireLock()) {
     return false;
 }
 if (isUnlockState()) {
     boolean allowSetExclusiveOwnerThread = !hasQueuedPredecessors() && compareAndSetState(0, acquires);
     if (allowSetExclusiveOwnerThread) {
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
     }
 }
 if (isExclusiveForCurrentThread()) {
     int nextc = getState() + acquires;
     if (nextc >= 0) {
         setState(nextc);
         return true;
     }
     throw new Error("Maximum lock count exceeded");
 }
 return false;
}

private boolean isUnlockState() {
 return getState() == UNLOCK_STATE;
}

private boolean canNotAcquireLock() {
 final int threadState = getState();
 boolean currentTheadIsExclusive = Thread.currentThread() == getExclusiveOwnerThread();
 return threadState != UNLOCK_STATE && !currentTheadIsExclusive;
}

private boolean isExclusiveForCurrentThread() {
 return Thread.currentThread() == getExclusiveOwnerThread();
}