CAS自旋锁与synchronized关键字的使用与区别
Posted 小鹏说
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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底层其实是从用户态切换到内核态,去向操作系统申请一把重量级锁,这时,没有抢到锁的其他所有线程就只能进入等待队列,等当前拿到这把锁的线程执行完,并将该锁释放之后,才会被再次唤醒激活,去再次竞争这把锁。
以上是关于CAS自旋锁与synchronized关键字的使用与区别的主要内容,如果未能解决你的问题,请参考以下文章
Java多线程常见面试题-第一节:锁策略CAS和Synchronized原理
synchronized的锁升级(偏向锁,自旋锁(cas),重量级锁)
Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS