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 時,只有同一個對象會使用同一把鎖,不同對象之間的鎖是不同的。
本系列推薦文章
- 並發第一課:Thread 詳解
- Java中用戶線程和守護線程區別這么大?
- 深入理解線程池 ThreadPool
- 線程池的7種創建方式,強烈推薦你用它...
- 池化技術到達有多牛?看了線程和線程池的對比嚇我一跳!
- 並發中的線程同步與鎖
- volatile 和 synchronized 的區別
- 輕量級鎖一定比重量級鎖快嗎?
- 這樣終止線程,竟然會導致服務宕機?
- SimpleDateFormat線程不安全的5種解決方案!
- ThreadLocal不好用?那是你沒用對!
- ThreadLocal內存溢出代碼演示和原因分析!
- Semaphore自白:限流器用我就對了!
- CountDownLatch:別浪,等人齊再團!
- CyclicBarrier:人齊了,司機就可以發車了!
關注公號「Java中文社群」查看更多有意思、漲知識的 Java 並發文章。