最近看到有人说可使用 CAS + volatile 实现同步代码块。
心想,确实是能够实现的呀!由于 AbstractQueuedSynchronizer(简称 AQS)内部就是经过 CAS + volatile(修饰同步标志位state) 实现的同步代码块。java
而且ReentrantLock就是基于AQS原理来实现同步代码块的;ReentrantLock源码学习和了解AQS原理能够参考:带你探索ReentrantLock源码的快乐 node
今天,我们就经过 CAS + volatile 实现一个 迷你版的AQS ;经过这个迷你版的AQS可使你们对AQS原理更加清晰。编程
经过了解CAS操做和volatile来聊聊为何使用它们实现同步代码块。安全
CAS是compare and swap的缩写,从字面上理解就是比较并更新;主要是经过 处理器的指令 来保证操做的原子性 。ide
CAS 操做包含三个操做数:性能
简单来讲:从内存位置V上取到存储的值,将值和预期值A进行比较,若是值和预期值A的结果相等,那么咱们就把新值B更新到内存位置V上,若是不相等,那么就重复上述操做直到成功为止。学习
例如:JDK中的 unsafe 类中的 compareAndSwapInt 方法:测试
unsafe.compareAndSwapInt(this, stateOffset, expect, update);
CAS是一种无锁化编程,是一种非阻塞的轻量级的乐观锁;相比于synchronized阻塞式的重量级的悲观锁来讲,性能会好不少 。优化
可是注意:synchronized关键字在不断的优化下(锁升级优化等),性能也变得十分的好。this
volatile是java虚拟机提供的一种轻量级同步机制。
经过了解CAS操做和volatile关键字后,才能够更加清晰的理解下面实现的同步代码的demo程序。
总述同步代码块的实现原理:
加锁/释放锁时使用CAS操做对同步标志位state进行更新;
** * 加锁,非公平方式获取锁 */ public final void lock() { while (true) { // CAS操做更新同步标志位 if (compareAndSetState(0, 1)) { // 将独占锁的拥有者设置为当前线程 exclusiveOwnerThread = Thread.currentThread(); System.out.println(Thread.currentThread() + " lock success ! set lock owner is current thread . " + "state:" + state); try { // 睡眠一小会,模拟更加好的效果 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 跳出循环 break; } else { // TODO 若是同步标志位是1,而且锁的拥有者是当前线程的话,则能够设置重入,但本方法暂未实现 if (1 == state && Thread.currentThread() == exclusiveOwnerThread) { // 进行设置重入锁 } System.out.println(Thread.currentThread() + " lock fail ! If the owner of the lock is the current thread," + " the reentrant lock needs to be set;else Adds the current thread to the blocking queue ."); // 将线程阻塞,并将其放入阻塞列表 parkThreadList.add(Thread.currentThread()); LockSupport.park(this); // 线程被唤醒后会执行此处,而且继续执行此 while 循环 System.out.println(Thread.currentThread() + " The currently blocking thread is awakened !"); } } }
/** * 释放锁 * * @return */ public final boolean unlock() { // 判断锁的拥有者是否为当前线程 if (Thread.currentThread() != exclusiveOwnerThread) { throw new IllegalMonitorStateException("Lock release failed ! The owner of the lock is not " + "the current thread."); } // 将同步标志位设置为0,初始未加锁状态 state = 0; // 将独占锁的拥有者设置为 null exclusiveOwnerThread = null; System.out.println(Thread.currentThread() + " Release the lock successfully, and then wake up " + "the thread node in the blocking queue ! state:" + state); if (parkThreadList.size() > 0) { // 从阻塞列表中获取阻塞的线程 Thread thread = parkThreadList.get(0); // 唤醒阻塞的线程 LockSupport.unpark(thread); // 将唤醒的线程从阻塞列表中移除 parkThreadList.remove(0); } return true; }
import sun.misc.Unsafe; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.LockSupport; /** * @PACKAGE_NAME: com.lyl.thread6 * @ClassName: AqsUtil * @Description: 使用 CAS + volatile 同步标志位 = 实现 迷你版AQS ; * <p> * <p> * 注意:本类只简单实现了基本的非公平方式的独占锁的获取与释放; 像重入锁、公平方式获取锁、共享锁等都暂未实现 * <p/> * @Date: 2021-01-15 10:55 * @Author: [ 木子雷 ] 公众号 **/ public class AqsUtil { /** * 同步标志位 */ private volatile int state = 0; /** * 独占锁拥有者 */ private transient Thread exclusiveOwnerThread; /** * JDK中的rt.jar中的Unsafe类提供了硬件级别的原子性操做 */ private static final Unsafe unsafe; /** * 存放阻塞线程的列表 */ private static List<Thread> parkThreadList = new ArrayList<>(); /** * 同步标志位 的“起始地址”偏移量 */ private static final long stateOffset; static { try { unsafe = getUnsafe(); // 获取 同步标志位status 的“起始地址”偏移量 stateOffset = unsafe.objectFieldOffset(AqsUtil.class.getDeclaredField("state")); } catch (NoSuchFieldException e) { throw new Error(e); } } /** * 经过反射 获取 Unsafe 对象 * * @return */ private static Unsafe getUnsafe() { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } catch (Exception e) { return null; } } /** * 加锁,非公平方式获取锁 */ public final void lock() { while (true) { if (compareAndSetState(0, 1)) { // 将独占锁的拥有者设置为当前线程 exclusiveOwnerThread = Thread.currentThread(); System.out.println(Thread.currentThread() + " lock success ! set lock owner is current thread . " + "state:" + state); try { // 睡眠一小会,模拟更加好的效果 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 跳出循环 break; } else { // TODO 若是同步标志位是1,而且锁的拥有者是当前线程的话,则能够设置重入,但本方法暂未实现 if (1 == state && Thread.currentThread() == exclusiveOwnerThread) { // 进行设置重入锁 } System.out.println(Thread.currentThread() + " lock fail ! If the owner of the lock is the current thread," + " the reentrant lock needs to be set;else Adds the current thread to the blocking queue ."); // 将线程阻塞,并将其放入阻塞队列 parkThreadList.add(Thread.currentThread()); LockSupport.park(this); // 线程被唤醒后会执行此处,而且继续执行此 while 循环 System.out.println(Thread.currentThread() + " The currently blocking thread is awakened !"); } } } /** * 释放锁 * * @return */ public final boolean unlock() { if (Thread.currentThread() != exclusiveOwnerThread) { throw new IllegalMonitorStateException("Lock release failed ! The owner of the lock is not " + "the current thread."); } // 将同步标志位设置为0,初始未加锁状态 state = 0; // 将独占锁的拥有者设置为 null exclusiveOwnerThread = null; System.out.println(Thread.currentThread() + " Release the lock successfully, and then wake up " + "the thread node in the blocking queue ! state:" + state); if (parkThreadList.size() > 0) { // 从阻塞列表中获取阻塞的线程 Thread thread = parkThreadList.get(0); // 唤醒阻塞的线程 LockSupport.unpark(thread); // 将唤醒的线程从阻塞列表中移除 parkThreadList.remove(0); } return true; } /** * 使用CAS 安全的更新 同步标志位 * * @param expect * @param update * @return */ public final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } }
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @PACKAGE_NAME: com.lyl.thread6 * @ClassName: SynCodeBlock * @Description: 简单的测试 * @Date: 2021-01-15 10:26 * @Author: [ 木子雷 ] 公众号 **/ public class SynCodeBlock { public static void main(String[] args) { // 10 个线程的固定线程池 ExecutorService logWorkerThreadPool = Executors.newFixedThreadPool(10); AqsUtil aqsUtil = new AqsUtil(); int i = 10; while (i > 0) { logWorkerThreadPool.execute(new Runnable() { @Override public void run() { test(aqsUtil); } }); --i; } } public static void test(AqsUtil aqsUtil) { // 加锁 aqsUtil.lock(); try { System.out.println("正常的业务处理"); } finally { // 释放锁 aqsUtil.unlock(); } } }
例如上面测试程序启动了10个线程同时执行同步代码块,可能此时只有线程 thread-2 获取到了锁,其他线程因为没有获取到锁被阻塞进入到了阻塞列表中;当获取锁的线程释放了锁后,会唤醒阻塞列表中的线程,而且是按照进入列表的顺序被唤醒;此时被唤醒的线程会再次去尝试获取锁,若是此时有新线程同时尝试获取锁,那么此时也存在竞争了,这就是非公平方式抢占锁(不会按照申请锁的顺序获取锁)。
上面的代码中没有实现线程自旋操做,下面看看该怎么实现呢?
首先说说为何须要自旋操做:
由于在某些场景下,同步资源的锁定时间很短,若是没有获取到锁的线程,为了这点时间就进行阻塞的话,就有些得不偿失了;由于进入阻塞时会进行线程上下文的切换,这个消耗是很大的;
使线程进行自旋的话就很大可能会避免阻塞时的线程上下文切换的消耗;而且通常状况下都会设置一个线程自旋的次数,超过这个次数后,线程还未获取到锁的话,也要将其阻塞了,防止线程一直自旋下去白白浪费CPU资源。
若是本文对您有帮助的话,请挥动下您爱发财的小手点下赞呀,您的支持就是我不断创做的动力,谢谢啦!
您能够VX搜索 【木子雷】 公众号,大量Java学习干货文章,您能够来瞧一瞧哟!