一、序言
前面我们提到的synchronized 等锁机制是一种阻塞同步,虽然它完成了我们的原子性操作,和线程安全,但是这种阻塞同步机制是比较耗费性能的,因为在阻塞和唤醒等状态转换中,是需要CPU指令进行帮忙实现,这要的调度是比较耗时的,因此这种策略是一种悲观策略,当然我们需要线程安全,又要高效,在一定情况下我们会采用非阻塞同步机制。
二、非阻塞同步
它的原理机制是基于冲突检测的乐观锁并发策略,简单的理解就是我们先干了再说,如果没有其他线程访问,那么我们的操作就顺利的完成,如果有其他线程访问,并且产生了冲突,那么我们就再来解决冲突。这样就不用把其他线程阻塞,大量的的进行线程状态的切换,这种操作就是非阻塞同步。
2.1 常见的非阻塞同步有:
a.volatile 变量:轻量级的线程同步,不会引起线程调度,提供可见性,但是不提供原子性
b.CAS 原子指令:轻量级线程同步,不会引起线程调度,提供可见性和原子性。
2.2 CAS 指令:
CAS 是建立在“硬件指令集”上来控制原子性的,因为我们要检测和解决多线程的原子性操作,又不想用阻塞机制,那么只能通过机器指令来完成,这里的指令操作应该是比上面锁机制的指令切换快的(我没做过硬件级的测试)。常用的原子指令操作有:
a.测试并设置(Test-and-Set)
b.获取并增加(Fetch-and-Increment)
c.交换(swap)
d:比较并交换(compare-and-swap,CAS)
e:加载链接/条件存储(Load-Linked/Stroe-Conditional,LL/SC)
后面两种是现在计算机常用的,还其他的指令,这里暂时不介绍了。我们知道提高并发性,应使得串行部分达到最大程度的并行,与锁相比,飞阻塞算法机制,是直接操作机器指令,从指令层面协调多线程的竞争,使得线程在竞争公共资源的时候不会发生阻塞,减少了线程调度的开销,因此速度是由于锁的。
2.3 CAS 的实现
非阻塞线程的实现,以CAS 为例,它需要3个操作数
a.变量的内存地址 A
b.变量的旧的预期值 V
c.变量的新值 B
在CAS 指令执行时,当V 的值符合A的预期的时候,新值B 才会更新,否则不会更新,这是一个原子操作。比如:A 地址指向V = 1,当一个线程准备更新V的值为B=2 额时候,检测到A 还是指向 V= 1的,那么允许修改,如果修改期间,检测到 A 已经指向 V = 其他值的时候,也就是说被其他线程改了就不会执行更新了。
三、非阻塞容器
在JDK 1.5 之后,JAVA 给我们提供了一些非阻塞容器,该操作由sun.misc.Unsafe 类里面的compareAndSwapInt 和 compareAndLong 等几个方法包装,JVM 内部对该方法做了特殊处理,及时编译出来的结果就是平台处理器相关的CAS指令。
常用的非阻塞算法的容器包括:ConcurrentLinkedQueue,SynchronousQueue,Exchanger 和 ConcurrentSkipListMap,包括J.U.C里面的整数原子类AtomicInteger等。在这里我们用一个简单的例子来尝试该原子类的用法,以及从其源码上分析它如何实现的。
public static AtomicInteger race = new AtomicInteger(); public static void main(String[] args) { // 这是自增方法,我们看看如何实现了 类似 i++ 的功能。 race.incrementAndGet(); }
public final int incrementAndGet() { for (;;) { // 先获得当前值 int current = get(); // 然后计算增加后的值,也就是我们的预期值 int next = current + 1; // 然后预期值和 当前值进行比较(看下面) if (compareAndSet(current, next)) return next; } } public final int get() { return value; } // 这里是通过当前类在内存中的值,valueOffset, // 期望的值expect,需要更新的值update // 进行比较,如果valueOffset 表示的值,和当前except 值等,那么我们执行修改, // 否则表示已经被其他线程更改了,不执行,循环继续。 public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
关于valueOffset 的值的取得,是通过Unsafe.getUnsafe()获得实例的 objectFieldOffset 方法获得的,是native 方法,JAVA 仅仅允许启动类加载(Bootstrap ClassLoader) 的类才能访问,反射也可以的,这是它内部代码:
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { // 这里可以定位当前类字段value在内存中的值 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
关于Unsafe 的源码可参考:http://www.docjar.com/html/api/sun/misc/Unsafe.java.html
3.1 CAS 的ABA 问题:
从上面CAS 的原理分析,假设变量i 的原始值是i=5,A 线程通过get() 方法,获取值等于V,然后这时候B线程用同样的方式获得V,然后改成了6,(中途可能被其他使用),然后又改回成5.这时候 A线程去判断的时候,发现内存值还是5,说明没有改变,就执行更新。但是我们发现 在中途其实已经改变过了,又改变回来了而已,这就是ABA 问题。
当然ABA 问题,表面上上不会影响你的业务逻辑,但是在有些情况下,发生这种中途 “调包” 的事情,还是会有影响。解决类似的问题的办法一般是加个版本号,更新了版本加1,每次比较的之后还要对版本进行比较,在JDK 里面是通过院子引用类“AtomicStampedReference” 进行处理的,具体的原理,我也没去看!
小结:
1.非阻塞同步,是通过底层指令,通过比较 交换等操作,保证变量安全。它的算法其实比较复杂的,这里仅仅对原子类进行分析,介绍了CAS 的原理,像上面提到的各种集合类等等,以后再介绍吧。
2.非阻塞算法种类有很多,我在并发实践上看,还有CAS2或者CASX操作的。但是这些 都没做介绍,关于这块的使用,虽说在一定程度上代替了锁机制,但是我认为仅仅能代替简单类型的变化操作,复杂的还是得用锁机制 以及 复杂的算法。
相关推荐
4. CAS 操作的原理分析:CAS 操作可以通过分析 CPU 底层指令来理解,例如 Intel x86 CPU 的 LOCK CMPXCHG 指令。 5. AtomicInteger 等原子变量类:JAVA 中的 AtomicInteger 等原子变量类使用 CAS 操作来实现原子增量...
学习非阻塞的同步机制CAS 现代的处理器都包含对并发的支持,其中最通用的方法就是比较并交换(compare and swap),简称CAS。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的...
以上总结了多线程面试中的常见问题及解决方案,包括但不限于`synchronized`关键字、CAS算法、`ReentrantLock`、`ReentrantReadWriteLock`、线程池、`ConcurrentHashMap`等核心概念和技术细节。这些知识点对于深入...
介绍非阻塞同步的基本概念,包括悲观锁和乐观锁的区别,以及比较并交换(CAS)机制的工作原理。 - **第31章:非阻塞算法的实现** 探索非阻塞数据结构的实现方法,如非阻塞栈和非阻塞链表,以及它们如何提高并发性能...
以上内容涵盖了Java中高并发和多线程面试可能遇到的问题,包括Synchronized关键字的使用、原理、锁定对象、可重入性、JVM对锁的优化、Synchronized的公平性和非公平性、以及锁消除和锁粗化的概念。掌握这些知识点...
- 非阻塞同步通过CAS(Compare and Swap)操作实现,避免了锁的开销。 - 乐观锁和悲观锁:乐观锁假设并发冲突少,悲观锁假设冲突多。 21. **非阻塞算法** - Lock-Free算法:基于CAS的无锁算法,实现高效的并发...
CAS(Compare and Swap)是一种无锁算法,在不使用锁的情况下实现多线程之间的变量同步。其工作原理是:比较变量的期望值和实际值是否一致,如果一致,则更新为新值;如果不一致,则不做任何操作。 #### 三、实战...
Java多线程和并发知识是Java开发中的重要组成部分,它涉及到如何高效地利用系统资源,尤其是在多核CPU环境下,合理地使用多线程可以显著提升应用程序的性能。 **1. 理论基础** 1.1 为什么需要多线程 多线程的引入...
- **高性能**:由于采用了非阻塞算法和CAS操作,相较于其他同步容器如`synchronized`或`ReentrantLock`,在高并发场景下性能更加优越。 #### 三、ConcurrentLinkedQueue的数据结构 `ConcurrentLinkedQueue`采用...
- 定义了一套多线程访问共享资源的同步器框架。 - **核心功能**: - 管理线程等待队列,包括等待队列的构建、等待节点的插入和移除。 - 提供了独占式和共享式两种同步器模板。 #### 八、notify()与notifyAll()的...
- 死锁是指两个或更多线程在等待彼此释放资源时发生的阻塞状态。可以通过算法(如Banker算法)来预防或检测死锁,确保系统资源的合理分配。 4. **竞态条件分析:** - 竞态条件是指多线程程序中,线程间执行顺序的...
ReentrantLock作为Java并发编程中的重要工具,它的灵活性和高效性使得它在多线程环境下被广泛使用。本篇文章将深入解析ReentrantLock的源码,重点讨论非公平锁和公平锁的获取过程。 1. **ReentrantLock的基本概念**...
在传统的多线程编程中,为了确保数据一致性,通常会使用锁来同步对共享资源的访问,但这种方式往往引入了额外的开销和潜在的死锁风险。 无锁编程是一种并发控制策略,它避免了锁的使用,转而依赖于原子操作和内存...
ReentrantLock的实现基于AbstractQueuedSynchronized(AQS)同步器,采用非阻塞的FIFO(先进先出)等待队列。当线程尝试获取锁失败时,会被添加到队列尾部。ReentrantLock分为公平锁和非公平锁,公平锁保证了线程...
- **ThreadLocal原理分析**:通过ThreadLocalMap实现线程局部变量的隔离。 - **线程池的实现原理**:维护一组工作线程,根据任务队列调度任务。 - **线程池的几种实现方式**:FixedThreadPool、CachedThreadPool等...
【3.1.4.AQS底层原理分析1】 在Java并发编程中,AbstractQueuedSynchronizer(AQS)是一个核心的同步组件,用于构建锁和同步器的基础框架。AQS是一个抽象类,它提供了线程同步的基本机制,包括线程的排队、等待和...
6. **阻塞队列BlockingQueue**:`14-阻塞队列BlockingQueue实战及其原理分析二-fox`讲解了阻塞队列的概念。 BlockingQueue是一种特殊的队列,当队列满时,生产者线程会被阻塞;队列空时,消费者线程会被阻塞。这种...
- **Java多线程**:Java通过Thread类和Runnable接口实现多线程,理解线程的创建、启动、同步和停止方法。 - **线程状态**:了解线程的五种基本状态:新建、就绪、运行、阻塞和死亡。 - **线程优先级**:理解Java...
在Java并发编程中,CAS(Compare and Swap)是一种无锁算法,用于在多线程环境中实现对共享变量的原子性更新。相比于传统的锁机制,如`synchronized`和`Lock`,CAS具有更低的开销,因为它避免了线程阻塞。本文将深入...
这一特性使得阻塞队列非常适合用于生产者-消费者模式的场景,例如在多线程环境中作为线程间通信的桥梁。 阻塞队列广泛应用于各种并发场景中,如任务调度、消息传递、资源管理等。通过合理利用阻塞队列,可以有效...