CAS自旋鎖與synchronized關鍵字的使用與區別
阿新 • • 發佈:2021-06-26
問題引入
本例子想要表達的是,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:4610 * @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底層其實是從使用者態切換到核心態,去向作業系統申請一把重量級鎖,這時,沒有搶到鎖的其他所有執行緒就只能進入等待佇列,等當前拿到這把鎖的執行緒執行完,並將該鎖釋放之後,才會被再次喚醒啟用,去再次競爭這把鎖。