樂觀鎖:
不會加鎖,只是更新共享資源時,判斷是否允許更新。
一. CAS(Compare and Swap)思想:
需要讀寫的內存值V,進行比較的預期值A,要寫入的新值B。
當且僅當預期值A和內存值V相同時,才允許將內存值V修改爲B;否則該線程將自己的A更新爲新的V值,繼續自旋等待,直至檢測到A值與V值相等時,才更新爲B。
使用 Unsafe.compareAndSwapObject() 保證線程安全用例
如下的用例中 UNSAFE.compareAndSwapObject(unsafeCASTest, I_OFFSET, unsafeCASTest.i, unsafeCASTest.i + 1); 完成了多線程安全的 i+1 操作。
/**
* @Author Snail
* @Describe 通過Unsafe對多個線程操作i++實現線程安全問題
* @CreateTime 2020/3/11
*/
public class UnsafeCASTest {
private int i = 0;
private static Unsafe UNSAFE;
static long I_OFFSET;//i的偏移量
static {
try {
//該方法無法獲取到UNSAFE類,需要通過下面的反射去獲取
// UNSAFE = Unsafe.getUnsafe();
// 獲取 Unsafe 內部的私有的實例化單例對象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 無視權限
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
I_OFFSET = UNSAFE.objectFieldOffset(UnsafeCASTest.class.getDeclaredField("i"));
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
@Test
public void main() {
UnsafeCASTest unsafeCASTest = new UnsafeCASTest();
new Thread(new ThreadAdd(unsafeCASTest)).start();
new Thread(new ThreadAdd(unsafeCASTest)).start();
new Scanner(System.in).nextLine();//bio阻塞當前執行線程
}
class ThreadAdd implements Runnable {
private final UnsafeCASTest unsafeCASTest;
public ThreadAdd(UnsafeCASTest unsafeCASTest) {
this.unsafeCASTest=unsafeCASTest;
}
@Override
public void run() {
while (true) {
// i++;
boolean b = UNSAFE.compareAndSwapObject(unsafeCASTest, I_OFFSET, unsafeCASTest.i, unsafeCASTest.i + 1);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "::" + i);
}
}
}
}
Java基於 CAS 提供的操作類
java.util.concurrent.atomic包下有AtomicInteger、AtomicBoolean、AtomicLong等,都使用了CAS的機制,使用CPU的執行來保證原子性
//關於AtomicInteger的一個簡單用例
AtomicInteger atomicInteger=new AtomicInteger(0);
int i = atomicInteger.addAndGet(2);
System.out.println(i);//2
System.out.println(atomicInteger);//2
int andAdd = atomicInteger.getAndAdd(3);
System.out.println(andAdd);//2
System.out.println(atomicInteger);//5
Q:CAS包含了Compare和Swap兩個操作,它又如何保證原子性呢?
A:CAS是由CPU支持的原子操作,其原子性是在硬件層面進行保證的。
Q:CAS 有那些缺點?
A:
1. ABA問題
2. 高競爭環境下,自旋對CPU資源的消耗
3. 不夠靈活,只能保證一個共享變量的原子操作,涉及到多個變量的同步時,CAS無法保證安全
二. 版本號控制:
當要提交一個更新的時候,比較讀取時候的版本號,如果版本號一致,允許提交更新,否則返回失敗
悲觀鎖:
開始操作時,就加鎖,操作完成後再釋放鎖。
Lock(ReentrantLock)與Synchronized的對比:
類別 | synchronized | Lock |
---|---|---|
存在層次 | Java的關鍵字,在jvm層面上 | 是一個類 |
鎖的釋放 | 1、以獲取鎖的線程執行完同步代碼,釋放鎖 2、線程執行發生異常,jvm會讓線程釋放鎖 (在其底層原理可以看到) | 必須在finally中釋放鎖,不然容易造成線程死鎖 |
鎖的獲取 | 假設A線程獲得鎖,B線程等待。如果A線程阻塞,B線程會一直等待 | Lock有多個鎖獲取的方式,如lock.lock(),lock.tryLock(), |
鎖當前狀態 | 無法判斷 | 可以判斷(trylock) |
鎖類型 | 可重入、不可中斷、非公平 | 可重入、可中斷、可公平(初始化時傳入布爾值) |
性能 | 少量同步 | 大量同步 |
鎖類型的解釋:
-
可重入鎖:在執行一個對象中所有同步方法時,不用再次去獲取鎖
-
公平鎖:按線程等待時間長短來獲取鎖,等待時間長的具有優先獲取鎖權利。
ReentrantLock默認非公平鎖,實例化時傳入boolean值決定是否使用公平鎖
/**
* 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();
}
- 可中斷鎖:如果線程A使用的是可中斷方式(lock.lockInterruptibly();),線程A在嘗試獲取鎖的過程中,如果在獲取不到鎖的過程中,被中斷,那麼線程A將能夠感知到這個中斷,而不是一直阻塞下去(如果是不可中斷的鎖,那麼線程A將一直阻塞)。
兩種鎖底層的實現:
synchronized:對應兩條指令 monitorenter(獲取鎖 和 monitorexit(釋放鎖) ,每一個 monitorenter 都對應兩個monitorexit,一個用於正常執行完成釋放鎖,一個用於執行異常時釋放鎖
ReentrantLock:主要使用 AbstractQueuedSynchronizer(AQS)中的 volatile 修飾的同步狀態位state、CAS 修改 state 和 CLH 的線程隊列實現的一種獨佔鎖。
在 JDK 6 後對鎖進行了優化,Synchronized 效率上升到與 Lock 持平。那你談談 JVM對鎖的優化:
使用如下的一些規則來優化:
自適應自旋鎖:爲避免線程間的頻繁切換,JVM按情況給出適合的自旋時間
鎖消除:對檢測到不可能存在共享數據競爭的鎖進行消除
鎖粗化:當一連串的操作都在對同一個對象加鎖時,JVM會擴大鎖範圍
偏向鎖:爲減少同一線程多次獲取同一個鎖的性能消耗
輕量級鎖:是在線程已有偏向鎖的狀態時,更新到輕量級鎖