1. 程式人生 > 其它 >CAS自旋鎖與synchronized關鍵字的使用與區別

CAS自旋鎖與synchronized關鍵字的使用與區別

問題引入

本例子想要表達的是,count++這行程式碼在彙編級別不是原子操作
要想解決這個問題,有兩種方式:
1、可以在count++外面加上synchronized程式碼塊
2、利用Java內部提供的AtomicInteger類

 1 package com.lzp.juc.cas;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.concurrent.CountDownLatch;
 6 
 7 /**
 8  * @Author LZP
 9  * @Date 2021/6/25 21:46
10 * @Version 1.0 11 */ 12 public class ProblemTest { 13 14 /** 15 * 本例子想要表達的是,count++這行程式碼在彙編級別不是原子操作 16 * 要想解決這個問題,有兩種方式: 17 * 1、可以在count++外面加上synchronized程式碼塊 18 * 2、利用Java內部提供的AtomicInteger類 19 */ 20 int count; 21 22 void m() { 23 for (int i = 0; i < 10000; i++) {
24 count++; 25 } 26 } 27 28 public static void main(String[] args) { 29 final int num = 10; 30 31 CountDownLatch cdl = new CountDownLatch(num); 32 33 ProblemTest v = new ProblemTest(); 34 35 List<Thread> threads = new ArrayList<>();
36 37 for (int i = 0; i < num; i++) { 38 threads.add(new Thread(() -> { 39 v.m(); 40 cdl.countDown(); 41 }, "thread-" + i)); 42 } 43 44 // 啟動執行緒 45 threads.forEach(Thread::start); 46 47 try { 48 cdl.await(); 49 } catch (InterruptedException e) { 50 e.printStackTrace(); 51 } 52 53 System.out.println(v.count); 54 } 55 56 }

方式一:加synchronized關鍵字

 1 package com.lzp.juc.cas;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.concurrent.CountDownLatch;
 6 
 7 /**
 8  * @Author LZP
 9  * @Date 2021/6/25 22:01
10  * @Version 1.0
11  *
12  * 示例一
13  * 10個執行緒
14  * 100000
15  * 總耗時:22ms
16  *
17  * 示例二
18  * 1000個執行緒
19  * 10000000
20  * 總耗時:502ms
21  *
22  * 示例三
23  * 10000個執行緒
24  * 100000000
25  * 總耗時:1602ms
26  *
27  * 通過三個數量級的測試與比較,不難發現:當執行緒數量少時,用CAS(自旋鎖)效率較高,而當執行緒數量達到10000時,
28  * 即此時執行緒數量已經很多了,發現加了synchronized程式碼塊的程式碼竟然比用AtomicInteger的效率更高
29  * 總結:
30  * 當執行緒數量較少,且執行緒任務執行時間也較短時,用CAS更好
31  * 當執行緒數量較多時(即競爭比較激烈),且執行緒任務執行時間也較長時,直接用synchronized關鍵字會更好,因為
32  * synchronized底層其實是從使用者態切換到核心態,去向作業系統申請一把重量級鎖,這時,沒有搶到鎖的其他所有
33  * 執行緒就只能進入等待佇列,等當前拿到這把鎖的執行緒執行完,並將該鎖釋放之後,才會被再次喚醒啟用,去再次競爭
34  * 這把鎖。
35  *
36  */
37 public class Solution1 {
38 
39     int count;
40 
41     void m() {
42         for (int i = 0; i < 10000; i++) {
43             synchronized (this) {
44                 count++;
45             }
46         }
47     }
48 
49     public static void main(String[] args) {
50         final int num = 10000;
51 
52         CountDownLatch cdl = new CountDownLatch(num);
53 
54         Solution1 v = new Solution1();
55 
56         List<Thread> threads = new ArrayList<>();
57 
58         for (int i = 0; i < num; i++) {
59             threads.add(new Thread(() -> {
60                 v.m();
61                 cdl.countDown();
62             }, "thread-" + i));
63         }
64 
65         long start = System.currentTimeMillis();
66 
67         // 啟動執行緒
68         threads.forEach(Thread::start);
69 
70         try {
71             cdl.await();
72         } catch (InterruptedException e) {
73             e.printStackTrace();
74         }
75 
76         System.out.println(v.count);
77 
78         long end = System.currentTimeMillis();
79 
80         System.out.println("總耗時:" + (end - start));
81     }
82 
83 }

方式二:使用Java中的AtomicInteger原子類操作API,底層實現:CAS

 1 package com.lzp.juc.cas;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.concurrent.CountDownLatch;
 6 import java.util.concurrent.atomic.AtomicInteger;
 7 
 8 /**
 9  * @Author LZP
10  * @Date 2021/6/25 22:01
11  * @Version 1.0
12  *
13  * 使用AtomicInteger類
14  * CAS 自旋鎖、樂觀鎖
15  *
16  * 示例一
17  * 10個執行緒
18  * 100000
19  * 總耗時:6ms
20  *
21  * 示例二
22  * 1000個執行緒
23  * 10000000
24  * 總耗時:216ms
25  *
26  * 示例三
27  * 10000個執行緒
28  * 100000000
29  * 總耗時:2929ms
30  */
31 public class Solution2 {
32 
33     AtomicInteger atomicInteger = new AtomicInteger(0);
34 
35     void m() {
36         for (int i = 0; i < 10000; i++) {
37             atomicInteger.incrementAndGet();
38         }
39     }
40 
41     public static void main(String[] args) {
42         final int num = 10000;
43 
44         CountDownLatch cdl = new CountDownLatch(num);
45 
46         Solution2 v = new Solution2();
47 
48         List<Thread> threads = new ArrayList<>();
49 
50         for (int i = 0; i < num; i++) {
51             threads.add(new Thread(() -> {
52                 v.m();
53                 cdl.countDown();
54             }, "thread-" + i));
55         }
56 
57         long start = System.currentTimeMillis();
58 
59         // 啟動執行緒
60         threads.forEach(Thread::start);
61 
62         try {
63             cdl.await();
64         } catch (InterruptedException e) {
65             e.printStackTrace();
66         }
67 
68         System.out.println(v.atomicInteger.get());
69 
70         long end = System.currentTimeMillis();
71 
72         System.out.println("總耗時:" + (end - start) + "ms");
73     }
74 
75 }

發現:

通過三個數量級的測試與比較,不難發現:當執行緒數量少時,用CAS(自旋鎖)效率較高,而當執行緒數量達到10000時,即此時執行緒數量已經很多了,發現加了synchronized程式碼塊的程式碼竟然比用AtomicInteger的效率更高

總結:

當執行緒數量較少,且執行緒任務執行時間也較短時,用CAS更好
當執行緒數量較多,且執行緒任務執行時間也較長時,直接用synchronized關鍵字會更好,因為synchronized底層其實是從使用者態切換到核心態,去向作業系統申請一把重量級鎖,這時,沒有搶到鎖的其他所有執行緒就只能進入等待佇列,等當前拿到這把鎖的執行緒執行完,並將該鎖釋放之後,才會被再次喚醒啟用,去再次競爭這把鎖。