劍指Offer(鎖)——synchronized基本使用方法

說到併發那麼就要說一下Synchronized和ReentrantLock等等方面相關的知識。

針對互斥鎖先來介紹一些引起線程安全問題的主要原因是什麼?

  • 存在共享數據(也稱臨界資源);
  • 存在多條線程共同操作共享數據。

解決上面的問題的方法時:同一個時刻有且只有一個線程在操作共享數據時候其他線程必須等到該線程處理完數據後再對共享數據進行操作。

因此引入了互斥鎖,其有以下幾個特性:

  • 互斥性

同一個時間只有一個線程持有某個對象鎖,通過這種特性來實現多線程的協調機制,這樣在同一時間只有一個線程對需要同步的代碼塊(複合操作)進行訪問。也被稱爲原子性。

  • 可見性

保證在鎖釋放之前對共享變量所作的修改對於隨後獲得該鎖的另外的線程時可見的(在獲得鎖的時候獲得最新共享變量的值),否則另外一個線程可能是本地緩存的某個副本上繼續操作導致不一致。

synchronized鎖住的是對象而不是代碼。

堆是線程共享的也是經常訪問操作的地方,因此給一個對象上合適的鎖時解決線程問題的關鍵。

同時鎖又分爲兩類:

  • 獲取對象鎖

獲取對象鎖的兩種用法:
1、同步代碼塊
synchronized(this),synchronized(實例),而我們所說的“鎖”,也就是小括號內的實例對象。
2、同步非靜態方法
synchronized(method),這裏的鎖指的是當前對象的實例對象。

下面是一個簡單的demo

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();
        } 
    }

    /**
     * 異步方法
     */
    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();
        }
    }
}

運行結果:
在這裏插入圖片描述
先來看一下A線程執行async()方法的結果:
在這裏插入圖片描述
因爲async()方法本身和內部屬性沒有被synchronized包圍可以正常運行。

看一下B線程執行的情況:
在這裏插入圖片描述
B線程執行的方法中,如果使用this鎖那麼被this鎖包着的按照順序去執行,沒有使用this鎖的就會異步正常執行。

看看C線程執行的方法,使用的是synchronized去修飾方法:
在這裏插入圖片描述
被synchronized包着的整個方法都是隻能一次讓一個線程去訪問。

  • 獲取類鎖

獲取類鎖有兩種方法:
1、同步代碼塊(synchronized(類.class)),鎖是小括號()中的類對象(class 對象)
2、同步靜態方法(synchronized static method) , 鎖是當前對象的類對象(class 對象)

下面是一個簡單的demo:

public class SyncThread implements Runnable {

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.startsWith("D")) {
            syncClassBlock1();
        } else if (threadName.startsWith("E")) {
            syncClassMethod1();
        }

    }


    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();
        }
    }
}

輸出結果如下:
在這裏插入圖片描述
使用的區別僅僅是隻有一個被static修飾,一個沒有被static修飾。

不難發現執行的流程和之前是一樣的,先來看一下D線程:D開始輸出的語句是異步執行,於是線程1和線程2打印出沒有被synchronized包着的,之後逐個獲取鎖按照順序打印出對應的語句:
在這裏插入圖片描述
E線程這次是鎖完全覆蓋,獲取整體的鎖按照順序執行:
在這裏插入圖片描述
總結一下對象鎖和類鎖:

  1. 有線程訪問對象的同步代碼塊時候另外的線程可以訪問該對象的非同步代碼塊;
  2. 如果鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊的時候,另一個訪問對象的同步代碼塊的線程會被阻塞;
  3. 如果鎖住的是同一個對象,一個線程在訪問對象的同步方法的時候,另一個訪問對象的同步方法的線程會被阻塞;
  4. 如果鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊的時候,另一個訪問對象的同步方法的線程會被阻塞,反之亦然;
  5. 同一個類的不同對象的鎖互不干擾;
  6. 類鎖也是一把特殊的對象鎖,所以同一個類的不同對象使用類鎖是同步的;
  7. 類鎖和對象鎖互不干擾。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章