1. 程式人生 > 其它 >synchronized 加鎖 this 和 class 的區別!

synchronized 加鎖 this 和 class 的區別!

synchronized 是 Java 語言中處理併發問題的一種常用手段,它也被我們親切的稱之為“Java 內建鎖”,由此可見其地位之高。然而 synchronized 卻有著多種用法,當它修飾不同物件時,其意義也是不同的,下面我們一起來看。

synchronized 用法

synchronized 可以用來修飾普通方法、靜態方法和程式碼塊

① 修飾普通方法

/**
 * synchronized 修飾普通方法
 */
public synchronized void method() {
    // .......
}

當 synchronized 修飾普通方法時,被修飾的方法被稱為同步方法,其作用範圍是整個方法,作用的物件是呼叫這個方法的物件。

② 修飾靜態方法

/**
 * synchronized 修飾靜態方法
 */
public static synchronized void staticMethod() {
    // .......
}

當 synchronized 修飾靜態的方法時,其作用的範圍是整個方法,作用物件是呼叫這個類的所有物件。

③ 修飾程式碼塊

為了減少鎖的粒度,我們可以選擇在一個方法中的某個部分使用 synchronized 來修飾(一段程式碼塊),從而實現對一個方法中的部分程式碼進行加鎖,實現程式碼如下:

public void classMethod() throws InterruptedException {
    // 前置程式碼...
    
    // 加鎖程式碼
    synchronized (SynchronizedExample.class) {
        // ......
    }
    
    // 後置程式碼...
}

以上程式碼在執行時,被修飾的程式碼塊稱為同步語句塊,其作用範圍是大括號“{}”括起來的程式碼塊,作用的物件是呼叫這個程式碼塊的物件。

但以上程式碼,除了可以加鎖 class 之外,還可以加鎖 this,具體示例如下:

public void classMethod() throws InterruptedException {
    // 前置處理程式碼...
    synchronized (this) {
        // ......
    }
    // 後置處理程式碼...
}

那問題來了,使用 synchronized 加鎖 this 和 class 的區別是什麼?不都是加鎖同一個類嗎?

答案還真不是,加鎖 this 和 class 區別還是很大的。下面我們通過以下 4 個示例,來看二者之間的區別。

1.加鎖 class 共享一個類例項

首先,我們建立 5 個執行緒,呼叫同一個物件下 synchronized 加鎖的 class 程式碼,具體示例如下:

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class SynchronizedExample {

    public static void main(String[] args) {
        // 建立當前類例項
        final SynchronizedExample example = new SynchronizedExample();
        // 建立 5 個執行緒執行任務
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 呼叫 synchronized 修飾的 class 方法
                        example.classMethod();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    /**
     * synchronized 修飾的 class 方法
     * @throws InterruptedException
     */
    public void classMethod() throws InterruptedException {
        synchronized (SynchronizedExample.class) {
            System.out.println(String.format("當前執行執行緒:%s,執行時間:%s",
                    Thread.currentThread().getName(), new Date()));
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

以上程式的執行結果如下:

從上述結果可以看出,這 5 個執行緒共享的是同一把鎖。

2.加鎖 class 建立多個例項

接下來,我們建立 5 個執行緒,呼叫不同物件下 synchronized 加鎖的 class 程式碼,具體示例如下:

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class SynchronizedExample {

    public static void main(String[] args) {
        // 建立 5 個執行緒執行任務
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 建立類例項
                        SynchronizedExample example = new SynchronizedExample();
                        // 呼叫 synchronized 修飾的 class 方法
                        example.classMethod();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    
    /**
     * synchronized 修飾的 class 方法
     * @throws InterruptedException
     */
    public void classMethod() throws InterruptedException {
        synchronized (SynchronizedExample.class) {
            System.out.println(String.format("當前執行執行緒:%s,執行時間:%s",
                    Thread.currentThread().getName(), new Date()));
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

以上程式的執行結果如下:

從上述結果可以看出,雖然是不同的物件,但它們使用的仍然是同一把鎖。

3.加鎖 this 共享一個類例項

接下來,我們建立 5 個執行緒,呼叫 synchronized 加鎖 this 的示例。首先我們這 5 個執行緒呼叫同一個物件的加鎖方法,示例程式碼如下:

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class SynchronizedExample {

    public static void main(String[] args) {
        // 建立當前類例項
        final SynchronizedExample example = new SynchronizedExample();
        // 建立 5 個執行緒執行任務
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 呼叫 synchronized 修飾的 this 方法
                        example.thisMethod();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    
    /**
     * synchronized 修飾的 this 方法
     * @throws InterruptedException
     */
    public void thisMethod() throws InterruptedException {
        synchronized (this) {
            System.out.println(String.format("當前執行執行緒:%s,執行時間:%s",
                    Thread.currentThread().getName(), new Date()));
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

以上程式的執行結果如下:

從上述結果可以看出,以上執行緒使用的都是同一把鎖。

4.加鎖 this 建立多個類例項

最後一個示例最為特殊,我們使用 synchronized 加鎖 this,讓這 5 個執行緒呼叫各自建立物件的方法,具體示例如下:

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class SynchronizedExample {

    public static void main(String[] args) {
        // 建立 5 個執行緒執行任務
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 建立(多個)類例項
                        SynchronizedExample example = new SynchronizedExample();
                        // 呼叫 synchronized 修飾的 this 方法
                        example.thisMethod();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    
    /**
     * synchronized 修飾的 this 方法
     * @throws InterruptedException
     */
    public void thisMethod() throws InterruptedException {
        synchronized (this) {
            System.out.println(String.format("當前執行執行緒:%s,執行時間:%s",
                    Thread.currentThread().getName(), new Date()));
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

以上程式的執行結果如下:

從上述結果可以看出,當使用 synchronized 加鎖 this 時,如果執行緒呼叫的不是同一個物件,那麼這些執行緒之間使用的鎖都是自己獨立的鎖,這個結果就和 synchronized 加鎖 class 的結果完全不同了。

總結

通過以上 4 個示例我們可以得出結論,當使用 synchronized 加鎖 class 時,無論共享一個物件還是建立多個物件,它們用的都是同一把鎖,而使用 synchronized 加鎖 this 時,只有同一個物件會使用同一把鎖,不同物件之間的鎖是不同的

本系列推薦文章

  1. 併發第一課:Thread 詳解
  2. Java中使用者執行緒和守護執行緒區別這麼大?
  3. 深入理解執行緒池 ThreadPool
  4. 執行緒池的7種建立方式,強烈推薦你用它...
  5. 池化技術到達有多牛?看了執行緒和執行緒池的對比嚇我一跳!
  6. 併發中的執行緒同步與鎖
  7. volatile 和 synchronized 的區別
  8. 輕量級鎖一定比重量級鎖快嗎?
  9. 這樣終止執行緒,竟然會導致服務宕機?
  10. SimpleDateFormat執行緒不安全的5種解決方案!
  11. ThreadLocal不好用?那是你沒用對!
  12. ThreadLocal記憶體溢位程式碼演示和原因分析!
  13. Semaphore自白:限流器用我就對了!
  14. CountDownLatch:別浪,等人齊再團!
  15. CyclicBarrier:人齊了,司機就可以發車了!

關注公號「Java中文社群」檢視更多有意思、漲知識的 Java 併發文章。

關注下面二維碼,訂閱更多精彩內容。

關注公眾號(加好友):


作者: 王磊的部落格
出處: http://vipstone.cnblogs.com/