貼一下關於synchronized的帖子:https://www.jianshu.com/p/d53bf830fa09
PS:個人覺得寫的非常不錯,非常推薦閱讀,有助於對多線程以及JMM的理解。
下面是我對synchronized的使用測試:
一、synchronized(this)
說明:synchronized用來同步自身對象
這個用法估計是很多初學者經常看到的用法(說的我自己好像不是初學者似的)。
下面就來猜下以下程序的執行結果吧。
執行類MainTest:
public class MainTest {
public static void main(String[] args) throws InterruptedException {
// 創建對象o1,o2
TestObject o1 = new TestObject();
TestObject o2 = new TestObject();
// 創建線程t1,t2
ThreadTest t1 = new ThreadTest(o1);
ThreadTest t2 = new ThreadTest(o2); // TODO 將o2替換成o1試試看
t1.start();
t2.start();
// 等待線程t1,t2執行完成
t1.join();
t2.join();
// t1,t2都執行完成之後,最終輸出結果
System.out.println(TestObject.i);
}
}
測試對象類ObjectTest:
public class ObjectTest {
public static int i = 0;
/**
* this
*/
public void add() {
synchronized (this) {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
}
線程實現類ThreadTest:
public class ThreadTest extends Thread {
private ObjectTest objectTest;
public ThreadTest(ObjectTest objectTest) {
this.objectTest = objectTest;
}
@Override
public void run() {
// this
objectTest.add();
}
}
運行結果:多次運行執行類之後,發現最終的結果值始終<200000;按照TODO裏操作後,最終值都=200000。
二、synchronized method
說明:synchronized用來聲明方法。
執行類和線程實現類不動,只修改測試對象類,繼續猜猜執行結果吧。
測試對象類ObjectTest:
public class ObjectTest {
public static int i = 0;
// TODO 按照一里的TODO修改試試看
public synchronized void add() {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
運行結果:多次運行之後,最終結果依然是<200000。按照TODO修改之後,最終值都=200000。
三、synchronized(class)
說明:synchronized用來同步類的對象類,即Class<T> clazz = T.class中的clazz,描述類對象信息的。
執行類和線程實現類不動,只修改測試對象類,繼續猜猜執行結果吧。
測試對象類ObjectTest:
public class ObjectTest {
public static int i = 0;
public void add() {
synchronized (ObjectTest.class) {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
}
運行結果:多次運行之後,運行結果都=200000。
四、static method synchronized代碼塊
說明:靜態方法中使用synchronized代碼塊
執行類和線程實現類不動,只修改測試對象類。
測試對象類ObjectTest:
public class ObjectTest {
public static int i = 0;
public static void add() {
synchronized (ObjectTest.class) {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
}
運行結果:多次運行之後,運行結果都=200000。
五、static synchronized method
說明:synchronized用來聲明靜態方法
執行類和線程實現類不動,只修改測試對象類。
測試對象類ObjectTest:
public class ObjectTest {
public static int i = 0;
public static synchronized void add6() {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
運行結果:多次運行之後,運行結果都=200000。
六、synchronized(object)
說明:synchronized同步對象,其實這個本質和一是一樣的,但是同步對象有區別。我下面例子直接用String來替代。
線程實現類不動,修改測試對象類,執行類。
測試對象類ObjectTest:
public class ObjectTest {
public static int i = 0;
private String key;
/**
* 鎖key
*/
public void add() {
String key = this.key;
synchronized (key) {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
// Getters And Setters
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
執行類MainTest:
public class MainTest {
public static void main(String[] args) throws InterruptedException {
// 創建對象o1,o2
ObjectTest o1 = new ObjectTest();
o1.setKey("A");
ObjectTest o2 = new ObjectTest();
o2.setKey("A"); // TODO 修改B試試看
// 創建線程t1,t2
ThreadTest t1 = new ThreadTest(o1);
ThreadTest t2 = new ThreadTest(o2);
t1.start();
t2.start();
// 等待線程t1,t2執行完成
t1.join();
t2.join();
// t1,t2都執行完成之後,最終輸出結果
System.out.println(ObjectTest.i);
}
}
運行結果:多次運行之後,運行結果都=200000;按照TODO修改之後,多次運行結果都<200000。
七、總結
一句話總結:修改同一對象時,多個線程訪問同一方法,並且取得相同的鎖,這時候是線程安全的。
【一】裏面兩個線程雖然訪問同一個方法,但是取得的鎖卻不是同一個,因爲o1,o2不是同一個對象,所以這時候線程是不安全的。改成TODO裏的寫法之後,取得的鎖是同一對象,所以這時候線程是安全的。
【二】、【六】的道理和【一】是一樣的。
【三】兩個線程訪問同一個方法,獲取的鎖是類的類對象,一個類對象就是對類本身的描述,這個肯定是相同的,所以也是線程安全的。
【四】、【五】的道理和【三】一樣。
最後,如果多線程訪問通過一個對象,並修改該對象,要保證線程安全,其中一種實現手法是:對該對象加鎖,即當一個線程拿到該對象鎖之後,其它線程要等待該線程釋放鎖之後,才能繼續操作。這個實現手法延伸到分佈式開發,也是一樣的,只不過這個時候需要協調程序來進行協調了,比如zookeeper的分佈式鎖。還有一種實現手法是:CAS,ConcurrentHashMap裏,進行擴容的時候,好像就有用的這種實現手法。對於線程安全,我知道的就這兩種實現手法,而且我只是大概的瞭解它們,並沒有實際的去實現過,哪怕是簡單的實現也沒有,之後會嘗試簡單的去實現它們。
注:上面的純屬個人理解,包括那句總結的話。理解不對或有疑問的地方,請多多指教。