synchronize——對象鎖和類鎖


最近在研究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. 實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM