java鎖淺析

鎖是什麼

java開發中進行併發編程時針對操做同一塊區域時,若是不加鎖會出現併發問題,數據不是本身預計獲得的值。我以爲有點像mysql事務中髒讀、不可重複讀、幻讀的問題。加鎖的目的是爲了保證同一時間只有我一我的操做同一個資源。java

如何在代碼裏面加鎖

jdk提供給了咱們不少鎖的實現方式,用於各類狀況鎖的使用:mysql

  1. 使用synchronized修飾方法、修飾代碼塊等;
  2. 使用ReentrantLock來獲取鎖;
  3. ReadWriteLock讀寫分開的讀寫鎖;
  4. ReentrantReadWriteLock;

這些鎖有什麼區別

  1. 實現原理不一樣 synchronized是鎖實現原理是jdk實現的:
public class SynchronizedDemo {
     public static void main(String[] args) {
        Object o = new Object();
        synchronized (o){
            System.out.println("ReentrantLockDemo");
        }
    }
}
複製代碼

在這裏插入圖片描述
使用synchronized修飾的代碼會在編譯時加上monitorenter、monitorexit進行修飾,那麼問題來了,爲何用這個修飾後就可以保證線程執行過程當中的安全呢? 由於jdk在執行monitorenter、monitorexit區塊的時候是保證原子性的,要麼執行完成要麼執行不完成。synchronized修飾的代碼塊有可視性、原子性、順序性(防止重排序)。

ReentrantLock是怎麼實現鎖的機制呢? 經過繼承AbstractQueuedLongSynchronizer(AQS)來進行鎖的,實現原理是AQS中有一個變量來控制是否獲取到了鎖,經過Unsafe的CAS操做來獲取鎖,從而保證線程安全。sql

那麼問題來了?CAS操做的ABA問題如何解決? concurrent包中有提供AtomicStampedReference來解決ABA問題,也就是在CAS操做的同時須要再增長版本的判斷,從而保證不出現ABA的問題。編程

public class SolveCAS {
    // 主內存共享變量,初始值爲1,版本號爲1
    private static AtomicStampedReference<Integer> atomicStampedReference = new
            AtomicStampedReference<>(1, 1);


    public static void main(String[] args) {
        // t1,指望將1改成10
        new Thread(() -> {
            // 第一次拿到的時間戳
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+" 第1次時間戳:"+stamp+" 值爲:"+atomicStampedReference.getReference());
            // 休眠5s,確保t2執行完ABA操做
            try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
            // t2將時間戳改成了3,cas失敗
            boolean b = atomicStampedReference.compareAndSet(1, 10, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+" CAS是否成功:"+b);
            System.out.println(Thread.currentThread().getName()+" 當前最新時間戳:"+atomicStampedReference.getStamp()+" 最新值爲:"+atomicStampedReference.getReference());
        },"t1").start();

        // t2進行ABA操做
        new Thread(() -> {
            // 第一次拿到的時間戳
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+" 第1次時間戳:"+stamp+" 值爲:"+atomicStampedReference.getReference());
            // 休眠,修改前確保t1也拿到一樣的副本,初始值爲1
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            // 將副本改成20,再寫入,緊接着又改成1,寫入,每次提高一個時間戳,中間t1沒介入
            atomicStampedReference.compareAndSet(1, 20, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+" 第2次時間戳:"+atomicStampedReference.getStamp()+" 值爲:"+atomicStampedReference.getReference());
            atomicStampedReference.compareAndSet(20, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName()+" 第3次時間戳:"+atomicStampedReference.getStamp()+" 值爲:"+atomicStampedReference.getReference());

        },"t2").start();

    }
}
複製代碼
  1. 使用場景不一樣安全

    ReadWriteLock可使用在讀多寫少的狀況,儘可能提高併發的能力 ReadWriteLock、synchronized使用的是獨佔鎖,可是jdk對synchronized在編譯時會有優化。bash

相關文章
相關標籤/搜索