本篇隨筆主要介紹 java 中 synchronized 關鍵字常用法,主要有以下四個方面:
1、實例方法同步
2、靜態方法同步
3、實例方法中同步塊
4、靜態方法中同步塊
我覺得在學習synchronized關鍵字之前,我們首先需要知道以下一點:Java 中每個實例對象對應一把鎖且每個實例對象只有一把鎖,synchronized 關鍵字是通過對相應的實例對象加鎖來實現同步功能的。
一、實例方法中使用 synchronized 加鎖
實例方法中默認被加鎖的對象是調用此方法的實例對象。
class ImmutableValue {
public synchronized void comeIn() throws InterruptedException{
System.out.println(Thread.currentThread().getName() + ": start");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + ": finish");
}
public void synchronized comeInIn() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ": start");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + ": finish");
}
}
public class TestImmutableValue {
public static void main(String[] args) {
ImmutableValue im = new ImmutableValue();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
im.comeIn();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
im.comeInIn();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "t2");
t1.start();
t2.start();
}
}
在上面的代碼中創建了兩個線程並分別命名為 t1, t2。調用了同一個對象 im 的兩個同步方法 comeIn 和 comeInIn, 執行結果如下:
在 t1 線程開始執行後,即使 t1 線程睡眠了5s,線程 t2 中的 comeInIn 方法仍然沒有得到執行。這是因為 t1 線程先執行的 comeIn 方法,持有了對象 im 的鎖,且 comeIn 方法並沒有執行完,對象 im 的鎖沒有被釋放,所以 comeInIn 方法無法對對象 im 加鎖,就無法繼續執行,只能等到 t1 線程中的 comeIn 方法執行完畢,釋放對象 im 的鎖,comeInIn 方法才能繼續執行。
但是如果 t1 線程調用的是對象 im 的 comeIn 方法,而 t2 線程調用的是我們聲明的另外一個 ImmutableValue 對象 im2 對象的 comeInIn 方法,則這兩個方法的執行是互不影響的。因為 t1 線程的 comeIn 方法要獲得 im 對象的鎖,而 t2 線程要獲得的是 im2 對象的鎖,兩個鎖並不是同一個鎖(Java中每個實例對象都有且只有一個鎖),所以這兩個方法執行互不影響。
二、靜態方法中使用 synchronized 加鎖
靜態方法中默認被加鎖的對象是此靜態方法所在類的 class 對象。
class staticMethodSynchronized {
public static synchronized void method1() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ": start");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + ": finish");
}
public static synchronized void method2() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ": start");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + ": finish");
}
}
public class TestStaticClassSynchronized {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
staticMethodSynchronized.method1();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
staticMethodSynchronized.method2();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "t2");
t1.start();
t2.start();
}
}
在上述代碼中創建了兩個線程並命名為 t1,t2。 t1,t2 線程調用了 staticMethodSynchronized 類的兩個靜態同步方法 method1 和 method2。執行結果如下:
在 t1 線程開始執行後,即使 t1 線程睡眠了5s,線程 t2 中的 method2 方法仍然沒有得到執行。這是因為 t1 線程先執行的 method1 方法,持有了staticMethodSynchronized 類對象的鎖,且 method1 方法並沒有執行完,staticMethodSynchronized 類對象的鎖沒有被釋放,所以 comeInIn 方法無法對staticMethodSynchronized 類對象加鎖,就無法繼續執行,只能等到 t1 線程中的 method1 方法執行完畢,釋放 staticMethodSynchronized 類對象的鎖,method2 方法才能繼續執行。
三、實例方法中使用 synchronized 關鍵字制造同步塊
同步塊中默認被加鎖的對象是此同步塊括號聲明中包含的對象。
class ImmutableValue {
public synchronized void comeIn() throws InterruptedException{
System.out.println(Thread.currentThread().getName() + ": start");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + ": finish");
}
public void comeInIn() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ": start");
synchronized(this) {
}
System.out.println(Thread.currentThread().getName() + ": finish");
}
}
public class TestImmutableValue {
public static void main(String[] args) {
ImmutableValue im = new ImmutableValue();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
im.comeIn();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
im.comeInIn();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "t2");
t1.start();
t2.start();
}
}
由以上代碼可以看到: 在 comeInIn 方法中,運用 synchronized(this) 制造同步塊,要執行同步塊內的代碼,就必須獲得 this 對象的鎖(調用 comeInIn 方法的對象)。
執行結果可能為:
由此執行結果可見:t1 線程先執行了 comeIn 方法,獲得了對象 im 的鎖,之後由於 t1 線程進入睡眠狀態,t2 線程得到運行,開始執行 comeInIn 方法,當執行到同步代碼塊時發現對象 im 已被加鎖,無法繼續執行。t1 線程睡眠結束之後繼續執行,結束後釋放對象 im 的鎖,t2 線程才能繼續執行。
四、靜態方法中使用 synchronized 關鍵字制造同步塊
同步塊中默認被加鎖的對象是此同步塊括號聲明中包含的對象。
class staticMethodSynchronized {
private static final Object OBJ = new Object();
public static void method1() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ": start");
synchronized(OBJ) {
System.out.println(Thread.currentThread().getName() + ": 獲得鎖");
System.out.println(Thread.currentThread().getName() + ": 釋放鎖");
}
System.out.println(Thread.currentThread().getName() + ": finish");
}
public static void method2() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ": start");
synchronized(OBJ) {
System.out.println(Thread.currentThread().getName() + ": 獲得鎖");
System.out.println(Thread.currentThread().getName() + ": 釋放鎖");
}
System.out.println(Thread.currentThread().getName() + ": finish");
}
}
public class TestStaticClassSynchronized {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
staticMethodSynchronized.method1();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
staticMethodSynchronized.method2();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "t2");
t1.start();
t2.start();
}
}
在上述代碼中,兩個靜態方法中的同步塊都要獲得對象 OBJ 的鎖才能繼續向下執行,執行結果可能如下:
若 t1 線程先獲得鎖,則必須等到 t1 釋放鎖之後,t2 線程中同步代碼塊及其之後的代碼才能繼續執行,t2 線程先獲得鎖,t1 線程同理。
總之,我認為我們只需抓住一點:Java 中每個實例對象對應一把鎖且每個實例對象只有一把鎖,synchronized 關鍵字是通過對相應的實例對象加鎖來實現同步功能的(靜態方法為對相應的 class 對象加鎖)。在執行 synchronized 方法或 synchronized 同步塊之前,我們只需判斷其需要獲得的對象的鎖是否可獲得,就可判斷此方法或同步塊是否可得到執行。