目錄java
在併發編程中存在線程安全問題,主要緣由有:
1.存在共享數據
2.多線程共同操做共享數據。關鍵字synchronized能夠保證在同一時刻,只有一個線程能夠執行某個方法或某個代碼塊,同時synchronized能夠保證一個線程的變化可見(可見性)編程
synchronized鎖的是對象,鎖的的對象多是this、臨界資源對象、class類對象
加鎖的目的是保證操做的原子性安全
本例中:多線程
/** * synchronized關鍵字 * 鎖對象。synchronized(this)和synchronized方法都是鎖當前對象。 */ package com.bernardlowe.concurrent.t01; import java.util.concurrent.TimeUnit; public class Test_01 { private int count = 0; private Object object = new Object(); // 1.synchronized代碼塊,鎖的是object,臨界資源對象 public void testSync1(){ synchronized(object){ System.out.println(Thread.currentThread().getName() + " count = " + count++); } } // 2.synchronized代碼塊,鎖的是當前對象this public void testSync2(){ synchronized(this){ System.out.println(Thread.currentThread().getName() + " count = " + count++); } } // 2.synchronized代碼,鎖的也是this public synchronized void testSync3(){ System.out.println(Thread.currentThread().getName() + " count = " + count++); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { final Test_01 t = new Test_01(); new Thread(new Runnable() { @Override public void run() { t.testSync3(); } }).start(); new Thread(new Runnable() { @Override public void run() { t.testSync3(); } }).start(); } }
/** * synchronized關鍵字 * 同步方法 - static * 靜態同步方法,鎖的是當前類型的類對象。在本代碼中就是Test_02.class */ package com.bernardlowe.concurrent.t01; import java.util.concurrent.TimeUnit; public class Test_02 { private static int staticCount = 0; public static synchronized void testSync4(){ System.out.println(Thread.currentThread().getName() + " staticCount = " + staticCount++); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void testSync5(){ synchronized(Test_02.class){ System.out.println(Thread.currentThread().getName() + " staticCount = " + staticCount++); } } }
若是在加鎖的時候須要對當前對象的訪問限定,建議鎖臨界資源(即鎖一個臨界資源),若是對當前鎖級別比較高的話,就鎖當前對象。建議都以同步代碼塊的方式進行開發,這樣能夠避免鎖的範圍過高併發
同步方法和非同步方法
提問:同步方法是否影響其餘線程調用非同步方法,或調用其餘鎖資源的同步方法?ide
代碼示例
m1是非同步方法,m2是同步方法,m3同步代碼塊,鎖的臨界資源,這段代碼的目的是爲了證實在調用同步方法m1時,m2,m3是否可以執行this
/** * synchronized關鍵字 * 同步方法 - 同步方法和非同步方法的調用 * 同步方法隻影響鎖定同一個鎖對象的同步方法。不影響其餘線程調用非同步方法,或調用其餘鎖資源的同步方法。 */ package com.bernardlowe.concurrent.t01; public class Test_04 { Object o = new Object(); public synchronized void m1(){ // 重量級的訪問操做。 System.out.println("public synchronized void m1() start"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("public synchronized void m1() end"); } public void m3(){ synchronized(o){ System.out.println("public void m3() start"); try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("public void m3() end"); } } public void m2(){ System.out.println("public void m2() start"); try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("public void m2() end"); } public static class MyThread01 implements Runnable{ public MyThread01(int i, Test_04 t){ this.i = i; this.t = t; } int i ; Test_04 t; public void run(){ if(i == 0){ t.m1(); }else if (i > 0){ t.m2(); }else { t.m3(); } } } public static void main(String[] args) { Test_04 t = new Test_04(); new Thread(new MyThread01(0, t)).start(); new Thread(new MyThread01(1, t)).start(); new Thread(new MyThread01(-1, t)).start(); } }
執行結果
線程
能夠看出,在執行同步方法m1時,m2,m3不受影響,依然能夠執行
結論:同步方法隻影響鎖定同一個鎖對象的同步方法。不影響其餘線程調用非同步方法,或調用其餘鎖資源的同步方法。
但有一點須要注意,儘可能在商業開發中避免同步方法。使用同步代碼塊。 細粒度解決同步問題。3d
這裏重入鎖分爲兩類:code
下面來看第一種:在同步方法裏面調用其餘同步方法
思考:調用m1()方法,m2()方法是否會執行?
/** *synchronized關鍵字 *同步方法 - 調用其餘同步方法 */ package com.bernardlowe.concurrent.t01; import java.util.concurrent.TimeUnit; public class Test_06 { synchronized void m1(){ // 鎖this System.out.println("m1 start"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } m2(); System.out.println("m1 end"); } synchronized void m2(){ // 鎖this System.out.println("m2 start"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m2 end"); } public static void main(String[] args) { new Test_06().m1(); } }
結果:在m1()方法調用時,m2()方法依然可執行。屢次調用同步代碼,鎖定同一個鎖對象,是可重入的
第二種狀況:子類同步方法覆蓋父類同步方法
思考:子類同步方法m()中,調用父類同步方法m(),是否可重入?
/** * synchronized關鍵字 * 同步方法 - 繼承 * 子類同步方法覆蓋父類同步方法。能夠指定調用父類的同步方法。 * 至關於鎖的重入。 */ package com.bernardlowe.concurrent.t01; import java.util.concurrent.TimeUnit; public class Test_07 { synchronized void m(){ System.out.println("Super Class m start"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Super Class m end"); } public static void main(String[] args) { new Sub_Test_07().m(); } } class Sub_Test_07 extends Test_07{ synchronized void m(){ System.out.println("Sub Class m start"); super.m(); System.out.println("Sub Class m end"); } }
結果:子類同步方法m()中,調用了父類同步方法m(),能夠重入
思考:當同步方法或同步代碼塊中發生異常,是否會影響其餘線程的執行?
下面來看一段代碼
/** * synchronized關鍵字 * 同步方法 - 鎖與異常 */ package com.bernardlowe.concurrent.t01; import java.util.concurrent.TimeUnit; public class Test_08 { int i = 0; synchronized void m(){ System.out.println(Thread.currentThread().getName() + " - start"); while(true){ i++; System.out.println(Thread.currentThread().getName() + " - " + i); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 當i==5時會拋出異常 if(i == 5){ i = 1/0; } } } public static void main(String[] args) { final Test_08 t = new Test_08(); new Thread(new Runnable() { @Override public void run() { t.m(); } }, "t1").start(); new Thread(new Runnable() { @Override public void run() { t.m(); } }, "t2").start(); } }
這段代碼中先運行了兩個線程t一、t2,當其中一個線程發生異常時,另一個線程是否能繼續執行
結果:當同步方法中發生異常的時候,自動釋放鎖資源。不會影響其餘線程的執行。
思考: 同步業務邏輯中,若是發生異常如何處理?
好比上面會發生異常的代碼中,能夠這樣
if(i == 5){ try { i = 1/0; } catch (Exception e) { i = 0; } }
代碼示例:8
思考:當一個線程執行同步方法時,另外一個線程修改了鎖對象,是否還能執行同步代碼塊
/** * synchronized關鍵字 * 鎖對象變動問題 */ package com.bernardlowe.concurrent.t01; import java.util.concurrent.TimeUnit; public class Test_13 { Object o = new Object(); void m(){ System.out.println(Thread.currentThread().getName() + " start"); synchronized (o) { while(true){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " - " + o); } } } public static void main(String[] args) { final Test_13 t = new Test_13(); new Thread(new Runnable() { @Override public void run() { t.m(); } }, "thread1").start(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } Thread thread2 = new Thread(new Runnable() { @Override public void run() { t.m(); } }, "thread2"); t.o = new Object(); thread2.start(); } }
結論:能夠看出,其餘線程依然能夠執行同步方法
由於同步代碼一旦加鎖後,那麼會有一個臨時的鎖引用執行鎖對象,和真實的引用無直接關聯。在鎖未釋放以前,修改鎖對象引用,不會影響同步代碼的執行。
注意:不要使用靜態常量做爲鎖對象
以下代碼,由於String常量池的問題,s1,s1是同一個對象,因此m1,m2方法鎖的是也同一個對象,m1同步方法被執行後,m2方法不會被執行
/** * synchronized關鍵字 * 常量問題 */ package com.bernardlowe.concurrent.t01; public class Test_14 { String s1 = "hello"; String s2 = "hello"; // String s2 = new String("hello"); // new關鍵字,必定是在堆中建立一個新的對象。 void m1(){ synchronized (s1) { System.out.println("m1()"); while(true){ } } } void m2(){ synchronized (s2) { System.out.println("m2()"); while(true){ } } } public static void main(String[] args) { final Test_14 t = new Test_14(); new Thread(new Runnable() { @Override public void run() { t.m1(); } }).start(); new Thread(new Runnable() { @Override public void run() { t.m2(); } }).start(); } }