最近在研究Java 多線程的只是,經常能看到synchronize關鍵字,以前只是一眼帶過,沒有細究,今天趁這個機會,整理下
synchronize作為多線程關鍵字,是一種同步鎖,它可以修飾以下幾種對象:
代碼塊:被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{ }里的代碼,作用的對象是調用這個代碼塊的對象;
方法:被修飾的方法稱為同步方法,其作用的范圍是整個方法,作用的對象是調用這個方法的對象
靜態方法:作用的范圍是整個靜態方法,作用的對象是這個類的所有對象
類:作用的范圍是synchronize后面括號里的部分,作用的對象是這個類的所有對象
一、synchronize關鍵字
1.修飾方法
1 synchronized public void getValue() { 2 System.out.println("getValue method thread name=" 3 + Thread.currentThread().getName() + " username=" + username 4 + " password=" + password); 5 }
2.修飾代碼塊
1 public void serviceMethod() { 2 try { 3 synchronized (this) { 4 System.out.println("begin time=" + System.currentTimeMillis()); 5 Thread.sleep(2000); 6 System.out.println("end end=" + System.currentTimeMillis()); 7 } 8 } catch (InterruptedException e) { 9 e.printStackTrace(); 10 } 11 }
3.修飾類
1 public static void printA() { 2 synchronized (Service.class) { 3 try { 4 System.out.println("線程名稱為:" + Thread.currentThread().getName() 5 + "在" + System.currentTimeMillis() + "進入printA"); 6 Thread.sleep(3000); 7 System.out.println("線程名稱為:" + Thread.currentThread().getName() 8 + "在" + System.currentTimeMillis() + "離開printA"); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 } 13 14 }
二、Java中的對象鎖和類鎖
借用網友對對象鎖和類鎖定義
1 一段synchronized的代碼被一個線程執行之前,他要先拿到執行這段代碼的權限, 2 在Java里邊就是拿到某個同步對象的鎖(一個對象只有一把鎖); 3 如果這個時候同步對象的鎖被其他線程拿走了,他(這個線程)就只能等了(線程阻塞在鎖池等待隊列中)。 4 取到鎖后,他就開始執行同步代碼(被synchronized修飾的代碼); 5 線程執行完同步代碼后馬上就把鎖還給同步對象,其他在鎖池中等待的某個線程就可以拿到鎖執行同步代碼了。 6 這樣就保證了同步代碼在統一時刻只有一個線程在執行。
三、synchronize的用法與實例
總結:
synchronize修飾非靜態方法、同步代碼塊的synchronize(this)和synchronize(非this對象)的用法鎖的是對象,線程想要執行對應的同步代碼,需要獲得對象鎖。
synchronize修飾靜態方法以及同步代碼塊的synchronize(類.class)用法鎖是類,線程想要執行對應的同步代碼,需要獲得類鎖。
1.首先看下非線程安全例子
1 public class Run { 2 3 public static void main(String[] args) { 4 5 HasSelfPrivateNum numRef = new HasSelfPrivateNum(); 6 7 ThreadA athread = new ThreadA(numRef); 8 athread.start(); 9 10 ThreadB bthread = new ThreadB(numRef); 11 bthread.start(); 12 13 } 14 15 } 16 17 class HasSelfPrivateNum { 18 19 private int num = 0; 20 21 public void addI(String username) { 22 try { 23 if (username.equals("a")) { 24 num = 100; 25 System.out.println("a set over!"); 26 Thread.sleep(2000); 27 } else { 28 num = 200; 29 System.out.println("b set over!"); 30 } 31 System.out.println(username + " num=" + num); 32 } catch (InterruptedException e) { 33 // TODO Auto-generated catch block 34 e.printStackTrace(); 35 } 36 } 37 38 } 39 40 41 class ThreadA extends Thread { 42 43 private HasSelfPrivateNum numRef; 44 45 public ThreadA(HasSelfPrivateNum numRef) { 46 super(); 47 this.numRef = numRef; 48 } 49 50 @Override 51 public void run() { 52 super.run(); 53 numRef.addI("a"); 54 } 55 56 } 57 58 59 60 class ThreadB extends Thread { 61 62 private HasSelfPrivateNum numRef; 63 64 public ThreadB(HasSelfPrivateNum numRef) { 65 super(); 66 this.numRef = numRef; 67 } 68 69 @Override 70 public void run() { 71 super.run(); 72 numRef.addI("b"); 73 } 74 75 }
運行結果:
1 a set over! 2 b set over! 3 b num=200 4 a num=200
修改HasSelfPrivateNum如下,方法用synchronize修飾如下:
1 class HasSelfPrivateNum { 2 3 private int num = 0; 4 5 synchronized public void addI(String username) { 6 try { 7 if (username.equals("a")) { 8 num = 100; 9 System.out.println("a set over!"); 10 Thread.sleep(2000); 11 } else { 12 num = 200; 13 System.out.println("b set over!"); 14 } 15 System.out.println(username + " num=" + num); 16 } catch (InterruptedException e) { 17 // TODO Auto-generated catch block 18 e.printStackTrace(); 19 } 20 } 21 22 }
運行結果是線程安全的:
1 b set over! 2 b num=200 3 a set over! 4 a num=100
由於sleep方法不會放棄對象鎖,需要等到時間結束,釋放鎖,下一個進程才能獲取到該對象鎖
實驗結論:兩個線程訪問同一個對象中的同步方法是一定是線程安全的。本實現由於是同步訪問,所以先打印出a,然后打印出b。
這里線程獲取的是HasSelfPrivateNum的對象實例的鎖——對象鎖。
2.多個對象多個鎖
1 public class Run { 2 3 public static void main(String[] args) { 4 5 HasSelfPrivateNum numRef1 = new HasSelfPrivateNum(); 6 HasSelfPrivateNum numRef2 = new HasSelfPrivateNum(); 7 8 ThreadA athread = new ThreadA(numRef1); 9 athread.start(); 10 11 ThreadB bthread = new ThreadB(numRef2); 12 bthread.start(); 13 14 } 15 16 }
運行結果:
1 a set over! 2 b set over! 3 b num=200 4 a num=200
這里是非同步的,因為線程athread獲得是numRef1的對象鎖,而bthread線程獲取的是numRef2的對象鎖,他們並沒有在獲取鎖上有競爭關系,因此,出現非同步的結果。
3.同步塊synchronize(this)
1 public class Run { 2 3 public static void main(String[] args) { 4 ObjectService service = new ObjectService(); 5 6 ThreadA a = new ThreadA(service); 7 a.setName("a"); 8 a.start(); 9 10 ThreadB b = new ThreadB(service); 11 b.setName("b"); 12 b.start(); 13 } 14 15 } 16 17 class ObjectService { 18 19 public void serviceMethod() { 20 try { 21 synchronized (this) { 22 System.out.println("begin time=" + System.currentTimeMillis()); 23 Thread.sleep(2000); 24 System.out.println("end end=" + System.currentTimeMillis()); 25 } 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } 29 } 30 } 31 32 33 class ThreadA extends Thread { 34 35 private ObjectService service; 36 37 public ThreadA(ObjectService service) { 38 super(); 39 this.service = service; 40 } 41 42 @Override 43 public void run() { 44 super.run(); 45 service.serviceMethod(); 46 } 47 48 } 49 50 51 class ThreadB extends Thread { 52 private ObjectService service; 53 54 public ThreadB(ObjectService service) { 55 super(); 56 this.service = service; 57 } 58 59 @Override 60 public void run() { 61 super.run(); 62 service.serviceMethod(); 63 } 64 }
運行結果:
1 begin time=1466148260341 2 end end=1466148262342 3 begin time=1466148262342 4 end end=1466148264378
這樣也是同步的,線程獲取的是同步塊synchronized (this)括號()里面的對象實例的對象鎖,這里就是ObjectService實例對象的對象鎖了。
4.synchronize(非this對象)
1 public class Run { 2 3 public static void main(String[] args) { 4 5 Service service = new Service("xiaobaoge"); 6 7 ThreadA a = new ThreadA(service); 8 a.setName("A"); 9 a.start(); 10 11 ThreadB b = new ThreadB(service); 12 b.setName("B"); 13 b.start(); 14 15 } 16 17 } 18 19 class Service { 20 21 String anyString = new String(); 22 23 public Service(String anyString){ 24 this.anyString = anyString; 25 } 26 27 public void setUsernamePassword(String username, String password) { 28 try { 29 synchronized (anyString) { 30 System.out.println("線程名稱為:" + Thread.currentThread().getName() 31 + "在" + System.currentTimeMillis() + "進入同步塊"); 32 Thread.sleep(3000); 33 System.out.println("線程名稱為:" + Thread.currentThread().getName() 34 + "在" + System.currentTimeMillis() + "離開同步塊"); 35 } 36 } catch (InterruptedException e) { 37 // TODO Auto-generated catch block 38 e.printStackTrace(); 39 } 40 } 41 42 } 43 44 class ThreadA extends Thread { 45 private Service service; 46 47 public ThreadA(Service service) { 48 super(); 49 this.service = service; 50 } 51 52 @Override 53 public void run() { 54 service.setUsernamePassword("a", "aa"); 55 56 } 57 58 } 59 60 61 class ThreadB extends Thread { 62 63 private Service service; 64 65 public ThreadB(Service service) { 66 super(); 67 this.service = service; 68 } 69 70 @Override 71 public void run() { 72 service.setUsernamePassword("b", "bb"); 73 74 } 75 76 }
不難看出,這里線程爭奪的是anyString的對象鎖,兩個線程有競爭同一對象鎖的關系,出現同步。
5.靜態synchronize同步方法
1 public class Run { 2 3 public static void main(String[] args) { 4 5 ThreadA a = new ThreadA(); 6 a.setName("A"); 7 a.start(); 8 9 ThreadB b = new ThreadB(); 10 b.setName("B"); 11 b.start(); 12 13 } 14 15 } 16 17 class Service { 18 19 synchronized public static void printA() { 20 try { 21 System.out.println("線程名稱為:" + Thread.currentThread().getName() 22 + "在" + System.currentTimeMillis() + "進入printA"); 23 Thread.sleep(3000); 24 System.out.println("線程名稱為:" + Thread.currentThread().getName() 25 + "在" + System.currentTimeMillis() + "離開printA"); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } 29 } 30 31 synchronized public static void printB() { 32 System.out.println("線程名稱為:" + Thread.currentThread().getName() + "在" 33 + System.currentTimeMillis() + "進入printB"); 34 System.out.println("線程名稱為:" + Thread.currentThread().getName() + "在" 35 + System.currentTimeMillis() + "離開printB"); 36 } 37 38 } 39 40 41 class ThreadA extends Thread { 42 @Override 43 public void run() { 44 Service.printA(); 45 } 46 47 } 48 49 50 class ThreadB extends Thread { 51 @Override 52 public void run() { 53 Service.printB(); 54 } 55 }
運行結果:
1 線程名稱為:A在1466149372909進入printA 2 線程名稱為:A在1466149375920離開printA 3 線程名稱為:B在1466149375920進入printB 4 線程名稱為:B在1466149375920離開printB
兩個線程在爭奪同一個類鎖,因此同步。
6.synchronize(class)
1 class Service { 2 3 public static void printA() { 4 synchronized (Service.class) { 5 try { 6 System.out.println("線程名稱為:" + Thread.currentThread().getName() 7 + "在" + System.currentTimeMillis() + "進入printA"); 8 Thread.sleep(3000); 9 System.out.println("線程名稱為:" + Thread.currentThread().getName() 10 + "在" + System.currentTimeMillis() + "離開printA"); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 16 } 17 18 public static void printB() { 19 synchronized (Service.class) { 20 System.out.println("線程名稱為:" + Thread.currentThread().getName() 21 + "在" + System.currentTimeMillis() + "進入printB"); 22 System.out.println("線程名稱為:" + Thread.currentThread().getName() 23 + "在" + System.currentTimeMillis() + "離開printB"); 24 } 25 } 26 }
運行結果:
1 線程名稱為:A在1466149372909進入printA 2 線程名稱為:A在1466149375920離開printA 3 線程名稱為:B在1466149375920進入printB 4 線程名稱為:B在1466149375920離開printB
兩個線程依舊在爭奪同一個類鎖,因此同步。
總結:
A. 無論synchronized關鍵字加在方法上還是對象上,如果它作用的對象是非靜態的,則它取得的鎖是對象;如果synchronized作用的對象是一個靜態方法或一個類,則它取得的鎖是對類,該類所有的對象同一把鎖。
B. 每個對象只有一個鎖(lock)與之相關聯,誰拿到這個鎖誰就可以運行它所控制的那段代碼。
C. 實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制