synchronized

問題:線程安全問題的主要誘因是什麼?java

---》算法

一、存在共享數據(也稱臨界資源)緩存

二、存在多條線程共同操做這些共享數據安全

 

解決問題的根本方法:多線程

  同一時刻有且只有一個線程在操做共享數據,其餘線程必須等到該線程處理完數據後再對共享數據進行操做(串行)app

 

互斥鎖的特性異步

  一、互斥性:在同一時間只容許一個線程持有某個對象鎖,經過這種特性來實現多線程的協調機制,這樣在同一實際只有一個線程對學院同步的代碼塊(複合操做)進行訪問。互斥性也成爲操做的原子性。jvm

  二、可見性:必須確保在鎖被釋放以前,對共享變量所作的修改,對於隨後得到該鎖的另外一個線程是可見的(則在得到鎖時應得到最新共享變量的值),不然另外一個線程多是在本地緩存的某個副本上繼續操做,從而引發不一致async

注意的是:synchronized鎖的不是代碼,鎖的都是對象ide

 

根據獲取的鎖的分類:獲取對象鎖獲取類鎖

  獲取對象鎖的兩種用法:

  一、同步代碼塊(synchronized(this),synchronized(類實例對象)),鎖時小括號中的實例對象

  二、同步非靜態方法(synchronized method), 鎖時當前對象的實例對象

  獲取類鎖的兩種方法:

  一、同步代碼塊(synchronized(類.class)),鎖是小括號()中的類對象(Class對象)

  二、同步靜態方法(synchronized static method) ,鎖是當前對象的類對象(Class對象)

 

 

public class SyncThread implements Runnable {

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.startsWith("A")) {
            async();
        } else if (threadName.startsWith("B")) {
            syncObjectBlock1();
        } else if (threadName.startsWith("C")) {
            syncObjectMethod1();
        } else if (threadName.startsWith("D")) {
            syncClassBlock1();
        } else if (threadName.startsWith("E")) {
            syncClassMethod1();
        }else if(threadName.startsWith("F")){
            syncObjectMethod2();
        }

    }

    /**
     * 異步方法
     */
    private void async() {
        try {
            System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法中有 synchronized(this|object) {} 同步代碼塊
     */
    private void syncObjectBlock1() {
        System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * synchronized 修飾非靜態方法
     */
    private synchronized void syncObjectMethod1() {
        System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private synchronized void syncObjectMethod2() {  // 注意這裏的syncObjectMethod2方法和syncObjectMethod1方法鎖的對象實例都是同一個!!!
        System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    private void syncClassBlock1() {
        System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (SyncThread.class) {
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private synchronized static void syncClassMethod1() {
        System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  

public class SyncDemo {
    public static void main(String... args) {
        SyncThread syncThread = new SyncThread();
        Thread A_thread1 = new Thread(syncThread, "A_thread1");
        Thread A_thread2 = new Thread(syncThread, "A_thread2");
        Thread B_thread1 = new Thread(syncThread, "B_thread1");
        Thread B_thread2 = new Thread(syncThread, "B_thread2");
        Thread C_thread1 = new Thread(syncThread, "C_thread1");
        Thread C_thread2 = new Thread(syncThread, "C_thread2");
        Thread D_thread1 = new Thread(syncThread, "D_thread1");
        Thread D_thread2 = new Thread(syncThread, "D_thread2");
        Thread E_thread1 = new Thread(syncThread, "E_thread1");
        Thread E_thread2 = new Thread(syncThread, "E_thread2");
        Thread F_thread1 = new Thread(syncThread, "F_thread1");
        Thread F_thread2 = new Thread(syncThread, "F_thread2");
        A_thread1.start();
        A_thread2.start();
        B_thread1.start();
        B_thread2.start();
        C_thread1.start();
        C_thread2.start();
        D_thread1.start();
        D_thread2.start();
        E_thread1.start();
        E_thread2.start();
        F_thread1.start();
        F_thread2.start();
    }
}

  

總結:

一、有線程訪問對象的同步代碼塊時,另外的線程能夠訪問該對象的非同步代碼塊

二、若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另外一個訪問對象的同步代碼塊的線程會被阻塞

三、若鎖住的是同一個對象,一個線程在訪問對象的同步方法時,另外一個訪問對象同步方法的線程會被阻塞

四、若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另外一個訪問對象同步方法的線程會被阻塞,反之亦然

五、同一個類的不一樣對象的對象鎖互不干擾

六、類鎖因爲也是一種特殊的對象鎖,所以表現和上述1,2,3,4一致,而因爲一個類只有一把對象鎖,因此同一個類的不一樣對象使用類鎖將會是同步的

七、類鎖和對象鎖互補干擾

 

JVM優化鎖的策略有哪一些?

自旋鎖

一、許多狀況下,共享數據的鎖定狀態持續時間較短,切換線程不值得

二、經過讓線程執行忙循環等待鎖的釋放,不讓出CPU

三、缺點:若鎖被其餘線程長時間佔用,會帶來許多性能上的開銷

 

自適應自旋鎖

一、自旋的次數再也不固定

二、由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定

(JVM會愈來愈聰明,預測的時間愈來愈精確)

 

鎖消除

更完全的優化

  一、JIT編譯時,對運行上下文進行掃描,去除不可能存在競爭的鎖

public class StringBufferWithoutSync {
    public void add(String str1, String str2) {
        //StringBuffer是線程安全,因爲sb只會在append方法中使用,不可能被其餘線程引用
        //所以sb屬於不可能共享的資源,JVM會自動消除內部的鎖
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
    }

    public static void main(String[] args) {
        StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
        for (int i = 0; i < 1000; i++) {
            withoutSync.add("aaa", "bbb");
        }
    }

}

  

鎖粗化

另外一種極端

一、經過擴大加鎖的範圍,避免反覆加鎖和解鎖

public class CoarseSync {
    public static String copyString100Times(String target){
        int i = 0;
        StringBuffer sb = new StringBuffer();
        while (i<100){
            sb.append(target); 
// 因爲append是同步方法,循環調用這個同步方法,
//會致使鎖的不斷加鎖和釋放鎖的操做,影響沒必要要的資源浪費。
//而這個時候jvm會把鎖進行粗化,粗化到整塊區域(紅色標記) } return sb.toString(); } }

  

自旋鎖,自適應自旋鎖,鎖消除,鎖粗化

 

synchronized的四種狀態

一、無鎖、偏向鎖、輕量級鎖、重量級鎖

鎖膨脹方向:無鎖---》偏向鎖---》輕量級鎖----》重量級鎖

 

偏向鎖:減小同一線程獲取鎖的代價

  一、大多數狀況下,鎖不存在多線程競爭,老是由同一線程屢次得到

  核心思想:若是一個線程得到了鎖,那麼鎖就進入偏向模式,此時Mark Word的結構也變爲偏向鎖結構,當該線程再次請求鎖時,無需再作任何同步操做,則獲取鎖的過程只須要檢查Mark Word的鎖標記位爲 偏向鎖以及當前線程Id等於Mark Word 的ThreadID便可,這樣就省去了大量有關鎖申請的操做。

  不適合於鎖競爭比較激烈的多線程場合

 

輕量級鎖

  輕量級鎖時由偏向鎖升級來的,偏向鎖運行在一個線程進入同步塊的狀況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級爲輕量級鎖

  適應的場景:線程交替執行同步塊

  若存在同一時間訪問同一鎖的狀況,就會到只輕量級鎖膨脹爲重量級鎖

 

問題:輕量級鎖的加鎖過程和減鎖過程是怎樣的?

----》

 

 

 

鎖的內存語義

  當線程釋放鎖時,Java內存模型會把該線程對應的本地內存中的共享變量刷新到主內存中;

  而當線程獲取鎖時,Java內存模型會把該線程對應的本地內存設置爲無效,從而使得被監控器包含的臨界區代碼必須從主內存中讀取共享變量

理解上:本地內存A就是指該A線程的棧幀裏面的displaced mark word ,因爲棧幀是線程私有的,其餘線程是沒法看到的,所以須要把這個值放到公共的地方,其餘線程才能看到,從而作出下一步動做。

 

 

 

CAS(Compare And Swap )  能夠簡單地理解爲:是一種無鎖的算法

相關文章
相關標籤/搜索