2.1synchronized同步方法

 

由前言:

在第一章已經出現了非線程安全的狀況。「非線程安全」其實會發生在多個線程同時對同一個對象中的實例變量進行訪問時發生。產生的結果就是髒讀(讀到被修改過的數據)。git

「線程安全」得到的實例變量是通過同步處理的,不會出現髒讀的狀況。github

2.1.1方法內的變量爲線程安全:

「非線程安全」的問題存在於實例變量中,若是將實例變量私有,則不存在「非線程安全」問題,所得就是線程安全了。安全

在方法內部聲明變量,是不存在「非線程安全」問題的。dom

變量所在類:異步

public class SafeVariable { public void addI(String username) { try { int num = 0; if(username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(1000); } else { num = 200; System.out.println("b set over"); } System.out.println(username + " num = " + num); } catch (InterruptedException e) { e.printStackTrace(); } } }

線程代碼1:async

public class Thread1 extends Thread { private SafeVariable sv ; public Thread1(SafeVariable sv) { this.sv = sv; } @Override public void run() { sv.addI("a"); } }

線程代碼2:ide

public class Thread2 extends Thread { private SafeVariable sv ; public Thread2(SafeVariable sv) { this.sv = sv; } @Override public void run() { sv.addI("b"); } }

執行代碼:函數

public class Main { public static void main(String[] args) { SafeVariable sv = new SafeVariable(); Thread1 thread1 = new Thread1(sv); thread1.start(); Thread2 thread2 = new Thread2(sv); thread2.start(); } }

執行結果:學習

能夠看出,方法中的變量不存在非線程安全問題,永遠都是線程安全的。這是方法內部的變量是私有的特性形成的。測試

2.1.2實例變量非線程安全:

若是多個線程同時訪問一個對象的實例變量,則可能會出現「非線程安全問題」。

有多個實例變量可能會出現交叉的狀況,若是僅有一個實例變量時可能會出現覆蓋的狀況。

實例變量所在類:

public class SafeVariable1 { private int num = 0; public void addI(String username) { try { if ("a".equals(username)) { num = 100; System.out.println("a set over!"); Thread.sleep(1000); } else { num = 200; System.out.println("b set over"); } System.out.println(username + " num = " + num); } catch (InterruptedException e) { e.printStackTrace(); } } }

線程代碼1:

public class Thread3 extends Thread { private SafeVariable1 sv ; public Thread3(SafeVariable1 sv) { this.sv = sv; } @Override public void run() { sv.addI("a"); } }

線程代碼2:

public class Thread4 extends Thread { private SafeVariable1 sv ; public Thread4(SafeVariable1 sv) { this.sv = sv; } @Override public void run() { sv.addI("b"); } }

執行代碼:

public class Main { public static void main(String[] args) { SafeVariable1 sv = new SafeVariable1(); Thread3 thread3 = new Thread3(sv); thread3.start(); Thread4 thread4 = new Thread4(sv); thread4.start(); } }

執行結果:

本例中,兩個線程同時訪問了一個沒有同步的方法,若是兩個線程同時操做業務對象中的實例變量。就有可能出現「非線程安全」問題。

此處:若想解決「非線程安全問題」,只須要在addI()方法前加關鍵字synchronized便可實現。

實例變量所在類:

public class SafeVariable1 { private int num = 0; synchronized public void addI(String username) { try { if ("a".equals(username)) { num = 100; System.out.println("a set over!"); Thread.sleep(1000); } else { num = 200; System.out.println("b set over"); } System.out.println(username + " num = " + num); } catch (InterruptedException e) { e.printStackTrace(); } } }

執行結果:

從上述能夠看出,在兩(多)個線程同時訪問同一個對象中的同步方法時,必定是線程安全的。

2.1.3多個對象多個鎖:

實例變量所在類:

public class SafeVariable2 { private int num = 0; synchronized public void addI(String username) { try { if("a".equals(username)) { num = 100; System.out.println("a set over"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over"); } System.out.println(username + " num = " + num); } catch (InterruptedException e) { e.printStackTrace(); } } }

線程代碼1:

public class Thread5 extends Thread { private SafeVariable2 sv; public Thread5(SafeVariable2 sv) { this.sv = sv; } @Override public void run() { sv.addI("a"); } }

線程代碼2:

public class Thread6 extends Thread { private SafeVariable2 sv; public Thread6(SafeVariable2 sv) { this.sv = sv; } @Override public void run() { sv.addI("b"); } }

執行代碼:

public class Main { public static void main(String[] args) { SafeVariable2 sv1 = new SafeVariable2(); SafeVariable2 sv2 = new SafeVariable2(); Thread5 thread5 = new Thread5(sv1); thread5.start(); Thread6 thread6 = new Thread6(sv2); thread6.start(); } }

執行結果:

該示例:兩個線程分別訪問了一個類的兩個實例,效果倒是異步的。

緣由:關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼或者方法(函數)看成鎖。

因此要想實現同步效果,必須是多個線程訪問同一個對象時纔可以實現,而此示例中倒是兩個線程訪問各自的對象,建立的也是各自對象的鎖,因此展示的結果天然爲異步的。

 

同步的單詞:synchronized      異步的單詞:asynchronized

 

2.1.4synchronized方法與鎖對象:

證實synchronized加鎖的是對象。

對象類:

public class MyObject { public void methodA() { try { System.out.println("begin methodA threadName = " + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } }

線程代碼1:

public class Thread7 extends Thread { private MyObject object; public Thread7(MyObject object) { this.object = object; } @Override public void run() { object.methodA(); } }

線程代碼2:

public class Thread8 extends Thread { private MyObject object; public Thread8(MyObject object) { this.object = object; } @Override public void run() { object.methodA(); } }

執行代碼:

public class Main { public static void main(String[] args) { MyObject object = new MyObject(); Thread7 thread7 = new Thread7(object); thread7.setName("A"); Thread8 thread8 = new Thread8(object); thread8.setName("B"); thread7.start(); thread8.start(); } }

執行結果:

修改後的對象類:

public class MyObject { synchronized public void methodA() { try { System.out.println("begin methodA threadName = " + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } }

修改後的結果:

上述對比發現:調用關鍵字synchronized聲明的方法必定是排隊運行的。那如今有猜測:同一個對象中的其餘非synchronized聲明的方法被調用時會怎麼樣呢?

對象類:

public class MyObject { synchronized public void methodA() { try { System.out.println("begin methodA threadName = " + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } public void methodB() { try { System.out.println("begin methodB threadName = " +Thread.currentThread().getName() + " begintime = " +System.currentTimeMillis()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } }

線程代碼1:

public class Thread9 extends Thread { private MyObject object; public Thread9(MyObject object) { this.object = object; } @Override public void run() { object.methodA(); } }

線程代碼2:

public class Thread10 extends Thread { private MyObject object; public Thread10(MyObject object) { this.object = object; } @Override public void run() { object.methodB(); } }

執行代碼:

public class Main { public static void main(String[] args) { MyObject object = new MyObject(); Thread9 thread9 = new Thread9(object); thread9.setName("A"); Thread10 thread10 = new Thread10(object); thread10.setName("B"); thread9.start(); thread10.start(); } }

執行結果:

能夠看出,雖然線程A先持有了object對象的鎖,可是線程B仍能夠徹底調用非synchronized類型的方法。

繼續測試,此時將線程methodB()也加上同步鎖synchronized關鍵字。

修改對象類:

public class MyObject { synchronized public void methodA() { try { System.out.println("begin methodA threadName = " + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public void methodB() { try { System.out.println("begin methodB threadName = " +Thread.currentThread().getName() + " begintime = " +System.currentTimeMillis()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } }

執行結果:

綜上得出結論:

  1. A線程先持有object對象的Lock鎖,B線程能夠以異步的方式調用object對象中的非synchronized類型的方法。
  2. A線程先持有object對象的Lock鎖,B線程若是在這時調用object對象中的synchronized類型的方法時,須要等待,也就是同步。

2.1.5髒讀:

在2.1.4中經過synchronized關鍵字在賦值的時候使用了同步,可是在取值時也有可能遇到意想不到的意外。這種狀況就是髒讀,發生髒讀的狀況是:在讀取實例變量時,此值已經被其餘線程修改過了。

公共類:

public class PublicVar { public String username = "A"; public String password = "AA"; synchronized public void setValue(String username,String password) { try { this.username = username; Thread.sleep(1000); this.password = password; System.out.println("setValue method thread name = " + Thread.currentThread().getName() + " | username = " + username + " | password = " + password ); } catch (InterruptedException e) { e.printStackTrace(); } } public void getValue() { System.out.println("getValue method thread name = " + Thread.currentThread().getName() + " | username = " + username + " | password = " + password ); } }

線程代碼:

public class Thread11 extends Thread { public PublicVar pv; public Thread11(PublicVar pv) { this.pv = pv; } @Override public void run() { pv.setValue("B","BB"); } }

執行代碼:

public class Main { public static void main(String[] args) { try { PublicVar pv = new PublicVar(); Thread11 thread11 = new Thread11(pv); thread11.start(); Thread.sleep(5000);//打印結果受此值大小影響
 pv.getValue(); } catch (InterruptedException e) { e.printStackTrace(); } } }

執行結果:

出現髒讀的緣由在於getValue()方法不是同步的,因此能夠在任意時刻進行調用。解決方法是加上synchronized關鍵字。

總結:

當A線程調用了anyObject對象聲明瞭synchronized關鍵字的X方法時,A線程就得到了X方法鎖,準確來講是得到了對象的鎖,因此其餘線程必須等待A線程執行完畢才能夠調用X方法,但B線程能夠隨意調用其餘的非synchronized同步方法。

當A線程調用anyObject對象聲明瞭synchronized關鍵字的X方法時,A線程就得到了X方法所在的對象的鎖,因此其餘線程必須等A線程執行完畢才能夠去調用X方法,而B線程若是調用聲明瞭synchronized關鍵字的非X方法時,必須等A線程將X方法執行完,也就是釋放對象鎖後才能夠調用。這時A線程已經執行了一個完整的任務,也就是說,username和password已經被同時賦值,不存在髒讀的基本環境。

2.1.6synchronized鎖重入:

關鍵字synchronized擁有鎖重入的功能,也就是在使用synchronized時,當一個線程獲得一個對象鎖後,再次請求此對象鎖時是能夠再次獲得該對象的鎖的。這也證實了在一個synchronized方法/塊內部調用本地其餘synchronized方法/塊時,是永遠能夠獲得鎖的。

服務類代碼:

public class Myservice { synchronized public void service1() { System.out.println("service1"); service2(); } synchronized public void service2() { System.out.println("service2"); service3(); } synchronized public void service3() { System.out.println("service3"); } }

線程代碼:

public class Thread12 extends Thread { @Override public void run() { Myservice myservice = new Myservice(); myservice.service1(); } }

執行代碼:

public class Main { public static void main(String[] args) { Thread12 thread12 = new Thread12(); thread12.start(); } }

執行結果:

可重入鎖概念:本身能夠再次得到本身的內部鎖,好比有1條線程得到了某個對象的鎖,此時這個對象鎖尚未釋放,當其再次想要得到這個對象的鎖的時候仍是能夠得到的,若是不可鎖重入,會致使進入死循環。

 可重入鎖也能夠出如今繼承中。

父類:

public class Father { public int i = 10; synchronized public void fatherWay() { try { i--; System.out.println("father print i = " + i); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }

子類:

public class Son extends Father { synchronized public void sonWay() { try { while (i > 0) { i--; System.out.println("son print i = " + i); Thread.sleep(1000); this.fatherWay(); } } catch (InterruptedException e) { e.printStackTrace(); } } }

線程代碼:

public class Thread13 extends Thread { @Override public void run() { Son son = new Son(); son.sonWay(); } }

執行代碼:

public class Main { public static void main(String[] args) { Thread13 thread13 = new Thread13(); thread13.start(); } }

執行結果:

上述示例代表:當存在父子類繼承時,子類徹底可使用鎖重入調用父類的同步方法。

2.1.7出現異常,鎖自動釋放:

當一個線程執行的代碼出現異常時,其所持有的鎖會自動釋放。

業務類代碼:

public class Service1 { synchronized public void testMethod() { if ("a".equals(Thread.currentThread().getName())) { System.out.println("ThreadName = " + Thread.currentThread().getName() + " run beginTime = " + System.currentTimeMillis()); int i = 1; while (i == 1) { if(("" + Math.random()).substring(0,8).equals("0.123456")) { System.out.println("ThreadName = " + Thread.currentThread().getName() + " run  exceptionTime = " + System.currentTimeMillis()); Integer.parseInt("a"); } } } else { System.out.println("Thread B run Time = " + System.currentTimeMillis()); } } }

線程代碼1:

public class Thread14 extends Thread { private Service1 service1; public Thread14(Service1 service1) { this.service1 = service1; } @Override public void run() { service1.testMethod(); } }

線程代碼2:

public class Thread15 extends Thread { private Service1 service1; public Thread15(Service1 service1) { this.service1 = service1; } @Override public void run() { service1.testMethod(); } }

執行代碼:

public class Main { public static void main(String[] args) { try { Service1 service1 = new Service1(); Thread14 a = new Thread14(service1); a.setName("a"); a.start(); Thread.sleep(5000); Thread15 b = new Thread15(service1); b.setName("b"); b.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }

執行結果:

能夠看到的是,當出現異常後,鎖就被釋放了,線程B可以進入方法正常打印。

 

2.1.8同步不具備繼承性:

同步是不能夠被繼承的:

父類業務類:

public class FatherService { synchronized public void fatherService() { try { System.out.println("int father 下一步 sleep begin  threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("int father 下一步 sleep end  threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } }

子類業務類:

public class SonService extends FatherService { @Override public  void fatherService() { try { System.out.println("int son 下一步 sleep begin  threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("int son 下一步 sleep end  threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); super.fatherService(); } catch (InterruptedException e) { e.printStackTrace(); } } }

線程代碼1:

public class Thread16 extends Thread { private SonService sonService; public Thread16(SonService sonService) { this.sonService = sonService; } @Override public void run() { sonService.fatherService(); } }

線程代碼2:

public class Thread17 extends Thread { private SonService sonService; public Thread17(SonService sonService) { this.sonService = sonService; } @Override public void run() { sonService.fatherService(); } }

執行代碼:

public class Main { public static void main(String[] args) { SonService sonService = new SonService(); Thread16 a = new Thread16(sonService); a.setName("A"); a.start(); Thread17 b = new Thread17(sonService); b.setName("B"); b.start(); } }

執行結果:

 由結果能夠看到,代碼的執行是非同步的,這也就說明了synchronized關鍵字是不能夠繼承的,此時爲了實現同步,必須在子類的方法上加上關鍵字synchronized才行。

修改後的子類業務類:

public class SonService extends FatherService { @Override public synchronized void fatherService() { try { System.out.println("int son 下一步 sleep begin  threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("int son 下一步 sleep end  threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); super.fatherService(); } catch (InterruptedException e) { e.printStackTrace(); } } }

 修改後的執行結果:

 

源碼地址:https://github.com/lilinzhiyu/threadLearning

本文內容是書中內容兼具本身的我的見解所成。可能在我的見解上會有諸多問題(畢竟知識量有限,致使認知也有限),若是讀者以爲有問題請大膽提出,咱們能夠相互交流、相互學習,歡迎大家的到來,心成意足,等待您的評價。

相關文章
相關標籤/搜索