在多線程的環境下,常常存在線程安全問題,這種問題產生的緣由在於:該是原子操做的代碼段被其餘的線程切割,從而引發的數據混亂問題。在本篇博客中將講述如何使用synchronized關鍵字保證代碼段的原子操做。java
無論synchronized以何種方式使用,都會對一個對象加鎖,這個對象也就是所謂的監視器安全
synchronized關鍵字具備一下特徵:多線程
(1)若是對持有相同鎖的synchronized方法或者代碼塊,同步執行(即排隊,執行完一個,另外一個才能執行)異步
(2)若是對持有不一樣鎖的synchronized方法或者代碼塊,異步執行ide
(3)synchronized與非synchronized方法或者代碼塊,異步執行性能
使用synchronized來修飾方法(非static方法),其實就是this對象加鎖測試
下面經過synchronized方法來看一下上述的三個特徵優化
首先定義一個操做類MyObject,在類中有三個方法init,alter,print,其中init,alter方法使用synchronized修飾,代碼以下:
this
package com.feng.example; public class MyObject { synchronized public void init() { try { System.out.println(Thread.currentThread().getName()+"init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"init end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public void alter() { try { System.out.println(Thread.currentThread().getName()+"alter begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"alter end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void printInfo() { try { System.out.println(Thread.currentThread().getName()+"print begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"print end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
定義三個線程,分別對MyObject進行處理:spa
package com.feng.example; public class MyThreadA extends Thread{ private MyObject object; public MyThreadA(MyObject object) { this.object = object; } @Override public void run() { object.init(); } }
package com.feng.example; public class MyThreadB extends Thread{ private MyObject object; public MyThreadB(MyObject object) { this.object = object; } @Override public void run() { object.alter(); } }
package com.feng.example; public class MyThreadC extends Thread{ private MyObject object; public MyThreadC(MyObject object) { this.object = object; } @Override public void run() { object.printInfo(); } }
測試類以下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); Thread a = new MyThreadA(object1); Thread b = new MyThreadB(object1); Thread c = new MyThreadC(object1); a.start(); b.start(); c.start(); } }
分析:在測試類中,三個線程都是以實例化對象object1做爲參數,那麼三個類處理的都是object1對象,由於MyThreadA,MyThreadB處理的object1對象的同步方法,他們持有的監視器都是object1對象,所以二者應該是同步的,即排隊執行,MyThreadC處理的是非synchronized方法,所以和另外兩個線程是異步執行的。因此init方法,和alter方法是順序執行(即一個執行完,另外一個在執行,可是誰在前取決於誰先搶到object1的鎖),print方法和init,alter方法是異步執行,能夠在任意位置輸出。
程序運行結果以下:
Thread-2print begin Thread-0init begin Thread-2print end Thread-0init end Thread-1alter begin Thread-1alter end
從本例中說明對持有相同鎖的多個線程在執行synchronized方法時同步執行。synchronized方法與非synchronized方法異步執行。
下面修改測試類,觀察若是線程處理的對象不是一個對象會出現什麼狀況:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); Thread a = new MyThreadA(object1); Thread b = new MyThreadB(object2); a.start(); b.start(); } }
分析:線程a操做的對象是object1,線程b操做的對象是object2。另外一層意思,線程a執行init方法是獲取的是object1對象的鎖,線程b執行alter方法時獲取的是object2對象的鎖。雖然兩個方法都是synchronized方法,可是二者想要獲取的鎖不一樣,所以也就不存在同步問題。
執行結果以下:
Thread-0init begin Thread-1alter begin Thread-0init end Thread-1alter end
從結果中能夠看出,線程0中init的操做被線程1的alter操做分隔了,說明二者異步執行。
從而證實了synchronized方法持有的是this對象鎖。對持有不一樣鎖對象的synchronized方法異步執行。
經過上面的實驗證實了synchronized關鍵字的3個特徵。
修改操做類,代碼以下:
package com.feng.example; public class MyObject { synchronized public void init() { try { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); alter(); System.out.println(Thread.currentThread().getName()+"====init end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public void alter() { try { System.out.println(Thread.currentThread().getName()+"====alter begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====alter end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void printInfo() { try { System.out.println(Thread.currentThread().getName()+"====print begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====print end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
測試類以下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { try { MyObject object1 = new MyObject(); Thread a = new MyThreadA(object1); Thread b = new MyThreadB(object1); a.start(); Thread.sleep(1000); b.start(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:實例化操做類對象object1,建立兩個線程a,b,a線程啓動,執行init方法,線程a獲取了object1對象的鎖,在init方法方法中睡眠2s,執行init方法1s後啓動線程b,b企圖得到object1對象鎖,因爲被線程a佔有,因此須要等待。2s事後init方法中調用alter方法,須要獲取object1對象的鎖,如今線程a已經得到了object1對象的鎖,能夠直接進入alter方法,這就叫課重入性。執行完alter方法以後,線程a釋放鎖,線程b再執行alter方法。
輸出結果以下:
Thread-0====init begin Thread-0====alter begin Thread-0====alter end Thread-0====init end Thread-1====alter begin Thread-1====alter end
建立父類:
package com.feng.example; public class Person { synchronized public void eat() { System.out.println("person eat"); } }
建立子類:
package com.feng.example; public class Student extends Person{ synchronized public void study() { System.out.println("student study"); super.eat(); System.out.println("study end"); } }
測試類:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { Student student = new Student(); Thread a = new MyThread(student); a.start(); } }
分析:啓動線程a,a獲取了student的鎖,執行study方法,在study方法中調用父類的eat方法,不須要從新獲取鎖便可進入同步方法。
執行結果以下:
student study person eat study end
修改操做類:
package com.feng.example; public class MyObject { synchronized public void init() { try { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); throw new InterruptedException(); } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public void alter() { try { System.out.println(Thread.currentThread().getName()+"====alter begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====alter end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
線程類使用上面的MyThreadA,MyThreadB,此處再也不列出
測試類以下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { try { MyObject object = new MyObject(); Thread a = new MyThreadA(object); Thread b = new MyThreadB(object); a.start(); Thread.sleep(1000); //此處是要保證線程a先執行 b.start(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:線程a執行首先獲取object對象鎖,執行init方法,執行完init時拋出異常,若是線程a不釋放鎖,那麼線程b就不會執行。只要線程b執行了,就說明拋出異常,線程鎖釋放。
運行結果以下:
Thread-0====init begin 拋出異常 java.lang.InterruptedException at com.feng.example.MyObject.init(MyObject.java:11) at com.feng.example.MyThreadA.run(MyThreadA.java:15) Thread-1====alter begin Thread-1====alter end
說明線程a拋出異常時,釋放了線程鎖
不具備繼承性主要體如今方法的重寫上,對父類的同步方法進行重寫,若是不加synchronized關鍵字就不是同步方法
經過程序驗證上述結論:
package com.feng.example; public class Person { synchronized public void eat() { System.out.println("person eat"); } }
package com.feng.example; public class Student extends Person{ public void eat() { try { System.out.println("student eat..."); Thread.sleep(4000); System.out.println("student eat end..."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public void study() { try { System.out.println("student study..."); Thread.sleep(2000); System.out.println("student study end..."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
定義兩個線程:
package com.feng.example; public class StudentThreadA extends Thread{ private Student student; public StudentThreadA(Student student) { this.student = student; } @Override public void run() { // TODO Auto-generated method stub super.run(); student.eat(); } }
package com.feng.example; public class StudentThreadB extends Thread{ private Student student; public StudentThreadB(Student student) { this.student = student; } @Override public void run() { // TODO Auto-generated method stub super.run(); student.study(); } }
測試類:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { Student student = new Student(); Thread a = new StudentThreadA(student); Thread b = new StudentThreadB(student); a.start(); b.start(); } }
執行結果以下:
student eat... student study... student study end... student eat end...
分析:從運行結果中能夠看出兩個方法並非同步輸出的,所以重寫的eat方法不是同步方法。
使用synchronized方法,存在明顯的性能問題,在一個方法中可能只有幾個語句須要同步,使用synchronized方法卻使整個方法都同步了。執行速度確定會慢,咱們能夠經過synchronized語句塊來優化程序。
舉例說明synchronized方法的性能問題。
定義操做類:
package com.feng.example; public class MyObject { private String data1; private String data2; synchronized public void init() { try { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); data1 = "長時間的處理後從服務端獲取的值data1"; //模擬從服務端取數據 data2 = "長時間的處理後從服務端獲取的值data2"; System.out.println(data1); System.out.println(data2); System.out.println(Thread.currentThread().getName()+"====init end"); } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
定義線程類:
package com.feng.example; public class MyThread extends Thread { private MyObject object; public MyThread(MyObject object) { this.object = object; } @Override public void run() { System.out.println(Thread.currentThread().getName()+":開始運行時間爲:"+System.currentTimeMillis()); object.init(); System.out.println(Thread.currentThread().getName()+":結束時間爲:"+System.currentTimeMillis()); } }
測試類:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThread(object); Thread b = new MyThread(object); a.start(); b.start(); } }
分析:在測試類中定義了兩個線程實例a,b 這兩個線程都以object實例做爲參數,對object進行操做。首先線程a執行,輸出線程a的開始執行時間,執行object對象的init方法,init方法是同步方法。在執行init的同時,線程b也啓動了,輸出線程b的開始執行時間,這時線程b企圖獲取object對象的鎖,因爲如今object的鎖被線程a佔用,因此必須等待線程a執行完init方法以後纔會得到。線程a休眠2秒,這2s主要是模擬長時間的操做(假設就是從服務端獲取數據),給成員變量賦值,打印兩個成員變量,運行完成,釋放鎖。輸出線程a的結束時間,線程b執行object對象的init的放,進行一些列的操做。
運行結果以下:
Thread-1:開始運行時間爲:1449471254353 Thread-1====init begin Thread-0:開始運行時間爲:1449471254366 長時間的處理後從服務端獲取的值data1 長時間的處理後從服務端獲取的值data2 Thread-1====init end Thread-1:結束時間爲:1449471256364 Thread-0====init begin 長時間的處理後從服務端獲取的值data1 長時間的處理後從服務端獲取的值data2 Thread-0====init end Thread-0:結束時間爲:1449471258365
查看這兩個線程都完成操做所用的總時間就是Thread-0的結束時間減去Thread-1開始運行時間:1449471258365-1449471254353 = 4012 花費了大約4s的時間。可是想服務端獲取數據徹底能夠異步去執行,這樣就能夠減小執行時間了。
修改操做對象MyObject的代碼,使用synchronized語句塊
package com.feng.example; public class MyObject { private String data1; private String data2; public void init() { try { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); String data1Temp = "長時間的處理後從服務端獲取的值data1"+Thread.currentThread().getName(); //模擬從服務端取數據 String data2Temp = "長時間的處理後從服務端獲取的值data2"+Thread.currentThread().getName(); synchronized(this) { data1 = data1Temp; data2 = data2Temp; } System.out.println(data1); System.out.println(data2); System.out.println(Thread.currentThread().getName()+"====init end"); } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
其線程類與測試類代碼不變,分析程序:
在測試類中建立兩個線程實例a,b 線程a與線程b同時執行object的init方法,因爲此方法是非synchronized方法,所以,兩隻異步執行,(二者誰先執行到synchronized語句塊不肯定)假設線程a先執行到synchronized語句塊,線程a得到了進入synchronized語句塊的鎖,當線程b執行到synchronized語句塊時,嘗試獲取鎖,但要獲得線程a執行完synchronized語句塊,線程a執行完synchronized語句塊後釋放鎖,線程b獲取鎖執行synchronized語句塊的內容。
執行結果以下:
Thread-1:開始運行時間爲:1449472151572 Thread-1====init begin Thread-0:開始運行時間爲:1449472151577 Thread-0====init begin 長時間的處理後從服務端獲取的值data1Thread-1 長時間的處理後從服務端獲取的值data2Thread-1 Thread-1====init end Thread-1:結束時間爲:1449472153572 長時間的處理後從服務端獲取的值data1Thread-0 長時間的處理後從服務端獲取的值data2Thread-0 Thread-0====init end Thread-0:結束時間爲:1449472153577
從執行結果中能夠看出,執行的總時間大約爲2s,相比於使用synchronized方法快了不少。程序的性能獲得了提高
總結:synchronized語句塊就是將同步的內容最小化,使須要同步的信息放到synchronized語句塊中同步執行,不須要同步的信息放到synchronized語句塊外異步執行。
synchronized語句塊的鎖對象能夠是任意的對象,不論是什麼對象只要記住文章開頭synchronized關機字的特徵便可。
下面比較一下this,synchronized方法,class做爲鎖的區別:
首先synchronized(this){ } 使用的鎖對象就是操做類自己,不如上例中在線程類中調用的是object.init()方法,說明init方法中的this指的就是object對象本身,所以使用this做爲鎖和synchronized方法沒有本質的區別,區別只在於synchronized(this){ }能夠將同步的範圍縮小,是同步的內容更加精確,提升程序的性能。
sychronized(MyObject.class){ },是將class對象做爲鎖,同一個類的實例都只有一個class文件,class文件是惟一的,也就是說若是使用class做爲鎖,即便調用的是此類不一樣實例對象的方法,都會呈現出同步的效果。
舉例說明:
定義操做類MyObject
package com.feng.example; public class MyObject { public void init() { try { synchronized(this) { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====init end"); } } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
定義線程類:
package com.feng.example; public class MyThread extends Thread { private MyObject object; public MyThread(MyObject object) { this.object = object; } @Override public void run() { object.init(); } }
測試類代碼以下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); Thread a = new MyThread(object1); Thread b = new MyThread(object2); a.start(); b.start(); } }
分析:因爲在MyObject類中使用的是this做爲鎖,在測試類中定義了兩個MyObject對象object1,object2,實例化線程時使用兩個不一樣的對象做爲參數。線程a調用的是object1.init()方法,線程b調用的是object2.init()方法。兩個線程在執行object.init()方法時,this表明的就不是一個對象,因此是異步執行。
運行結果以下:
Thread-0====init begin Thread-1====init begin Thread-0====init end Thread-1====init end
下面修改MyObject操做類的代碼:將synchronized(this){ } 改成synchronized(MyObject.class){ }
package com.feng.example; public class MyObject { public void init() { try { synchronized(MyObject.class) { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====init end"); } } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:兩個線程a,b 線程a執行的是object1.init()方法,線程b執行的object2.init()方法,在執行到synchronized(MyObject.class){ }時,誰先執行到,誰就得到此類文件的class鎖,因爲object1和object2的class文件是同一個,所以兩個線程在synchronized語句塊處同步執行。
程序運行結果以下:
Thread-0====init begin Thread-0====init end Thread-1====init begin Thread-1====init end
只要是對象均可以做爲鎖,修改上例中的MyObject類:
package com.feng.example; public class MyObject { private String lock = new String(); public void init() { try { synchronized(lock) { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====init end"); } } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:兩個線程共用一個object對象,object中lock是object的成員變量,也就是說兩個線程共用一個鎖。
所以兩個線程在synchronized語句塊處同步執行。
運行結果以下:
Thread-0====init begin Thread-1====init begin 長時間的處理後從服務端獲取的值data1Thread-0 長時間的處理後從服務端獲取的值data2Thread-0 Thread-0====init end 長時間的處理後從服務端獲取的值data1Thread-1 長時間的處理後從服務端獲取的值data2Thread-1 Thread-1====init end
字符串通常是存放在常量池中的,若是兩個字符串表示的內容同樣,兩個字符串指向的是同一個對象。舉例說明:
修改操做類MyObject代碼
package com.feng.example; public class MyObject { public void init() { String str = "我是鎖"; try { synchronized(str) { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====init end"); } } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:在init方法中str是一個局部變量,在方法內部局部變量都有本身的臨時空間,各個方法互不干擾,按常理來講,兩個線程a,b應該想獲取的鎖不是同一個鎖,可是這裏是String類型,String類型的內容是放在常量池中的,在本程序中,str雖然是方法內的局部變量,但在不一樣的方法中局部變量都是指向的同一個對象,所以表示的是同一個鎖。
所以不建議使用字符串做爲鎖對象
程序運行結果以下:
Thread-0====init begin Thread-0====init end Thread-1====init begin Thread-1====init end
synchronized方法對應synchronized(this){ }語句塊,一樣的synchronized static 方法對應synchronized(class){ }語句塊
舉例說明:修改上述程序的操做類代碼:
package com.feng.example; public class MyObject { synchronized static public void init() { try { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====init end"); } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:兩個線程雖然執行的是不一樣對象的init方法,可是init方法是static的synchronized方法,兩個對象的class文件是同一個,所以兩個線程同步執行(與synchronized(MyObject.class){ }一個效果),運行結果以下
Thread-0====init begin Thread-0====init end Thread-1====init begin Thread-1====init end
定義操做類MyObject
package com.feng.example; public class MyObject { synchronized static public void init() { try { System.out.println(Thread.currentThread().getName() + "====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "====init end"); } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public void print() { try { System.out.println(Thread.currentThread().getName() + "====print begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "====print end"); } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
定義兩個線程類:
package com.feng.example; public class MyThreadA extends Thread{ private MyObject object; public MyThreadA(MyObject object) { this.object = object; } @Override public void run() { object.init(); } }
package com.feng.example; public class MyThreadB extends Thread{ private MyObject object; public MyThreadB(MyObject object) { this.object = object; } @Override public void run() { object.print(); } }
測試類以下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThreadA(object); Thread b = new MyThreadB(object); a.start(); b.start(); } }
運行結果以下:
Thread-1====print begin Thread-0====init begin Thread-1====print end Thread-0====init end
從運行結果能夠看出,class鎖與對象鎖不是同一個鎖,二者異步執行
調用多個synchronized方法的髒讀問題
若是在程序中連續調用幾個synchronized方法,其中存在分支判斷就會出現髒讀的問題,下面舉例說明:
定義一個只能存一個數據的對象MyObject,判斷當數據的個數<1時添加元素,其中獲取元素個數,添加元素都爲synchronized方法。
定義操做類:
package com.feng.example; import java.util.ArrayList; import java.util.List; public class MyObject { private List<String> list = new ArrayList<String>(); synchronized public void add() { list.add("xu"); } synchronized public int size() { return list.size(); } }
定義線程類:
package com.feng.example; public class MyThread extends Thread { private MyObject object; public MyThread(MyObject object) { this.object = object; } @Override public void run() { try { //保證object中的list中只有一個數據 if(object.size() < 1) { Thread.sleep(2000); object.add(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
測試類以下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThread(object); Thread b = new MyThread(object); a.start(); b.start(); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(object.size()); } }
分析:兩個線程a,b在執行時,線程a執行完size( )執行add()方法前釋放鎖,此時,線程b獲取鎖執行了size()方法,兩個線程同時進入了if(object.size()<1)的語句塊,兩個線程均可以往object對象中添加數據,因此添加了兩條數據,然而咱們定義的確是只能保存一個數據的對象。所以程序出現了問題。
程序運行結果以下:
2
修改程序將線程類的run方法中使用synchronized語句塊。
修改線程類:
package com.feng.example; public class MyThread extends Thread { private MyObject object; public MyThread(MyObject object) { this.object = object; } @Override public void run() { try { synchronized(object) { //保證object中的list中只有一個數據 if(object.size() < 1) { Thread.sleep(2000); object.add(); } } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
程序運行結果:
1
在程序的運行過程當中,鎖對象有可能會發生變化,當鎖對象發生變化時會出現什麼狀況呢?
定義操做類:
package com.feng.example; public class MyObject { private String str = "hahah"; public void init() { try { synchronized(str) { System.out.println(Thread.currentThread().getName() + "====init begin"); str = "hehhe"; //鎖對象發生了變化 Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "====init end"); } } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
定義線程類:
package com.feng.example; public class MyThread extends Thread { private MyObject object; public MyThread(MyObject object) { this.object = object; } @Override public void run() { object.init(); } }
定義測試類:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThread(object); Thread b = new MyThread(object); a.start(); b.start(); } }
分析:線程a,b執行同一個對象的object.init( )方法,二者誰先執行到synchronized(str)是不肯定的,假設線程a先到,那麼a獲取到str對象鎖,執行synchronized語句塊,接着修改了鎖對象str = "hehhe",此時線程b執行到語句塊synchronized(str),這裏str執行的是hehhe了,不在是hahah,二者不是同一個鎖,所以會異步執行
執行結果:
Thread-1====init begin Thread-0====init begin Thread-1====init end Thread-0====init end
修改操做類MyObject對象的代碼:
package com.feng.example; public class MyObject { private String str = "hahah"; public void init() { try { synchronized(str) { System.out.println(Thread.currentThread().getName() + "====init begin"); Thread.sleep(2000); str = "hehhe"; //鎖對象發生了變化 System.out.println(Thread.currentThread().getName() + "====init end"); } } catch (InterruptedException e) { System.out.println("拋出異常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:線程a,b執行同一個對象的object.init( )方法,二者誰先執行到synchronized(str)是不肯定的,假設線程a先到,線程a獲取了str的鎖,而後休眠2s,在執行str="hehhe"以前,線程b執行到synchronized(str),二者搶的是同一個對象的鎖,所以會同步執行。
運行結果以下;
Thread-0====init begin Thread-0====init end Thread-1====init begin Thread-1====init end
總結:當線程運行到synchronized語句塊是,鎖對象是哪個對象,就會保持哪個對象的鎖。