synchronized Java語言的關鍵字,可用來給對象和方法或者代碼塊加鎖,當它鎖定一個方法或者一個代碼塊的時候,同一時刻最多只有一個線程執行這段代碼。當兩個併發線程訪問同一個對象object中的這個加鎖同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊
synchronized
Java語言的關鍵字,可用來給對象和方法或者代碼塊加鎖,當它鎖定一個方法或者一個代碼塊的時候,同一時刻最多只有一個線程執行這段代碼。當兩個併發線程訪問同一個對象object中的這個加鎖同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。然而,當一個線程訪問object的一個加鎖代碼塊時,另一個線程仍然可以訪問該object中的非加鎖代碼塊。
——以上來源百度百科
一、方法內的變數為線程安全
“非線程安全”的問題存在於“實例變數”中,如果是方法內部的私有變數,則不會存在“非線程安全”問題,所得結果就是“線程安全”的了。
MyService類
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class MyService { 4 public void add(String name) { 5 try { 6 int num = 0; 7 if (name.equals("a")) { 8 num = 100; 9 System.out.println("a is over"); 10 Thread.sleep(1000); 11 } else { 12 num = 200; 13 System.out.println("b is over"); 14 } 15 System.out.println(name + " num = " + num); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 } 20 }
線程類A
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class ThreadA extends Thread { 4 private MyService service; 5 6 public ThreadA(MyService service) { 7 super(); 8 this.service = service; 9 } 10 11 @Override 12 public void run() { 13 service.add("a"); 14 } 15 }
線程類B
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class ThreadB extends Thread { 4 private MyService service; 5 6 public ThreadB(MyService service) { 7 super(); 8 this.service = service; 9 } 10 11 @Override 12 public void run() { 13 service.add("b"); 14 } 15 }
運行類
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class Run { 4 public static void main(String[] args) { 5 MyService service = new MyService(); 6 ThreadA threadA = new ThreadA(service); 7 threadA.start(); 8 ThreadB threadB = new ThreadB(service); 9 threadB.start(); 10 } 11 }
結果
1 a is over 2 b is over 3 b num = 200 4 a num = 100
從運行結果來看,方法中的變數不存在非線程安全的問題,永遠都是線程安全的,這事方法內部的變數是私有的特性造成的。
二、實例變數非線程安全
MyService類
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class MyService { 4 private int num = 0; 5 public void add(String name) { 6 try { 7 8 if (name.equals("a")) { 9 num = 100; 10 System.out.println("a is over"); 11 Thread.sleep(1000); 12 } else { 13 num = 200; 14 System.out.println("b is over"); 15 } 16 System.out.println(name + " num = " + num); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 }
線程類和運行類同上,運行結果
1 a is over 2 b is over 3 b num = 200 4 a num = 200
產生這個結果的原因是兩個線程同事訪問同一個沒有同步的方法,如果兩個對象同時操作對象中的實例變數,可能會造成非線程安全的問題
最簡單的解決方案是在方法的前面加個synchronized同步鎖
MyService類
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class MyService { 4 private int num = 0; 5 synchronized public void add(String name) { 6 try { 7 8 if (name.equals("a")) { 9 num = 100; 10 System.out.println("a is over"); 11 Thread.sleep(1000); 12 } else { 13 num = 200; 14 System.out.println("b is over"); 15 } 16 System.out.println(name + " num = " + num); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 }
線程類和運行類同上,運行結果
1 a is over 2 a num = 100 3 b is over 4 b num = 200
在兩個線程訪問同一個對象中的同步方法時一定是線程安全的,上面的代碼由於時同步訪問,所以先列印出a,然後在列印出b
改一下運行類,其他類同上
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class Run { 4 public static void main(String[] args) { 5 MyService serviceA = new MyService(); 6 MyService serviceB = new MyService(); 7 ThreadA threadA = new ThreadA(serviceA); 8 threadA.start(); 9 ThreadB threadB = new ThreadB(serviceB); 10 threadB.start(); 11 } 12 }
結果
1 a is over 2 b is over 3 b num = 200 4 a num = 100
這是兩個線程分別訪問同一個類的兩個不同實例的相同名稱的同步方法,效果卻是以非同步的形式來執行的。
因為創建了兩個業務對象,在系統中產生了兩個鎖,所以運行結果是非同步的。
關鍵字synchronized所取得的鎖都是對象鎖,而不是把一段代碼或者方法當作鎖。
My Service類
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class MyService { 4 synchronized public void methodA() { 5 try { 6 System.out.println("begin methodA threadName = " + Thread.currentThread().getName()); 7 Thread.sleep(3000); 8 System.out.println("end methodA time = " + System.currentTimeMillis()); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 } 13 14 public void methodB() { 15 try { 16 System.out.println("begin methodB threadName = " + Thread.currentThread().getName()); 17 Thread.sleep(3000); 18 System.out.println("end methodB time = " + System.currentTimeMillis()); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 } 23 }
線程類A
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class ThreadA extends Thread { 4 private MyService service; 5 6 public ThreadA(MyService service) { 7 super(); 8 this.service = service; 9 } 10 11 @Override 12 public void run() { 13 service.methodA(); 14 } 15 }
線程類B
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class ThreadB extends Thread { 4 private MyService service; 5 6 public ThreadB(MyService service) { 7 super(); 8 this.service = service; 9 } 10 11 @Override 12 public void run() { 13 service.methodB(); 14 } 15 }
運行類
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class Run { 4 public static void main(String[] args) { 5 MyService service = new MyService(); 6 ThreadA threadA = new ThreadA(service); 7 threadA.start(); 8 ThreadB threadB = new ThreadB(service); 9 threadB.start(); 10 } 11 }
結果
1 begin methodA threadName = Thread-1 2 begin methodB threadName = Thread-2 3 end methodB time = 1458400534384 4 end methodA time = 1458400534384
在My Service的methodB前面也加上關鍵字synchronized
My Service類
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class MyService { 4 synchronized public void methodA() { 5 try { 6 System.out.println("begin methodA threadName = " + Thread.currentThread().getName()); 7 Thread.sleep(3000); 8 System.out.println("end methodA time = " + System.currentTimeMillis()); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 } 13 14 synchronized public void methodB() { 15 try { 16 System.out.println("begin methodB threadName = " + Thread.currentThread().getName()); 17 Thread.sleep(3000); 18 System.out.println("end methodB time = " + System.currentTimeMillis()); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 } 23 }
結果
1 begin methodA threadName = Thread-1 2 end methodA time = 1458400619034 3 begin methodB threadName = Thread-2 4 end methodB time = 1458400622035
對比上面兩次代碼的運行結果。
在第一次運行時,當A線程先持有My Service的同步鎖時,B線程可以已非同步的方式去調用My Service對象中的非synchronized方法。
在第二次運行時,當A線程先持有My Service的同步鎖時,當B想調用My Service對象中的synchronized,則需要先等A釋放對象鎖。
所以,synchronized鎖住的時對象,而不是其他的一些東西。
三、synchronized鎖重入
當一個線程獲得一個對象鎖後,再次請求此對象鎖時是可以再次獲得此對象鎖的
My Service類
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class MyService { 4 synchronized public void methodA() { 5 System.out.println("methodA"); 6 methodB(); 7 } 8 9 synchronized public void methodB() { 10 System.out.println("methodB"); 11 methodC(); 12 } 13 14 synchronized public void methodC() { 15 System.out.println("methodC"); 16 } 17 }
線程類
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class MyThread extends Thread { 4 @Override 5 public void run() { 6 MyService myService = new MyService(); 7 myService.methodA(); 8 } 9 }
運行類
1 package com.mythread.www.day8.testSyn.ep1; 2 3 public class Run { 4 public static void main(String[] args) { 5 MyThread myThread = new MyThread(); 6 myThread.start(); 7 } 8 }
結果
1 methodA 2 methodB 3 methodC
自己還可以重新獲得自己的內部鎖,如果不可以的話,上面的這個Demo則會造成死鎖現象
Main類
1 package com.weishiyao.learn.day4.testThread; 2 3 public class Main { 4 public int i = 10; 5 synchronized public void operateIMainMethod() { 6 try { 7 i--; 8 System.out.println("main print i=" + i); 9 Thread.sleep(100); 10 } catch (Exception e) { 11 e.printStackTrace(); 12 } 13 } 14 }
Sub類
1 package com.weishiyao.learn.day4.testThread; 2 3 public class Sub extends Main{ 4 synchronized public void operateISubMethod() { 5 try { 6 while (i > 0) { 7 i--; 8 System.out.println("sub print i=" + i); 9 Thread.sleep(100); 10 this.operateIMainMethod(); 11 } 12 } catch (Exception e) { 13 e.printStackTrace(); 14 } 15 } 16 }
線程類
1 package com.weishiyao.learn.day4.testThread; 2 3 public class MyThread extends Thread{ 4 @Override 5 public void run() { 6 Sub sub = new Sub(); 7 sub.operateISubMethod(); 8 } 9 }
運行類
1 package com.weishiyao.learn.day4.testThread; 2 3 public class Run { 4 public static void main(String[] args) { 5 MyThread t = new MyThread(); 6 t.run(); 7 } 8 }
結果
1 sub print i=9 2 main print i=8 3 sub print i=7 4 main print i=6 5 sub print i=5 6 main print i=4 7 sub print i=3 8 main print i=2 9 sub print i=1 10 main print i=0
當存在父子類繼承關係時,子類完全可以通過父類“可重入鎖”調用父類的同步方法