• 两个重要的实现模型 AQS和CAS


    目录

    AQS模型

    AQS的核心思想

     AQS原理

    ​编辑

     CAS模型

     CAS 的特性:

     CAS 存在的问题:

     典型案例:原子量

    具体实现:

    原子量实现的计数器

    CAS模型的问题

    1、ABA问题

     2、CAS应用场景隐含竞争是短暂的,否则不断的自旋尝试会过度消耗CPU

    3、CAS只能保证一个共享变量的原子操作,解决方法是使用锁或者合并多个变量

     总结:


    AQS模型

    AbstractQueuedSynchronizer抽象队列同步器:AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。AQS维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)抽象队列同步器中包括两个部分:临时资源和一个FIFO的CLH阻塞队列.

    AQS的核心思想

    如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资
    源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。内部使用AQS的例子:以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

     AQS原理

    1. public class ReentrantLock implements Lock, java.io.Serializable {
    2. private final Sync sync; //锁的具体实现,依赖于同步器实现锁机制
    3. //内部类:
    4. abstract static class Sync extends AbstractQueuedSynchronizer //同步器实际上就是AQS的一个子实现
    5. static final class NonfairSync extends Sync//非公平同步器的实现类
    6. static final class FairSync extends Sync// 公平同步器的实现类

     CAS模型

    Java5中引入了AutomicInteger、AutomicLong、AutomicReference等特殊的原子性变量类,它们提供的如compareAndSet、incrementAndSet和getAndIncrement等方法都使用了CAS操作。都是由硬件指令来保证的原子方法。

    CAS即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置V、预期原值A和新值B。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。

    比较和交换(Compare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。这是作为单个原子操作完成的。

    原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成

     CAS 的特性:

    • 通过调用JNI的代码实现
    • 非阻塞算法
    • 非独占锁

     CAS 存在的问题:

    • ABA
    • 循环时间长开销大
    • 只能保证一个共享变量的原子操作

     典型案例:原子量

    所谓的原子量即操作变量的操作是原子的,该操作不可再分,因此是线程安全的

    为何要使用原子变量呢,
    原因是多个线程对单个变量操作也会引起一些问题。

    在Java5之前,可以通过volatile、synchronized关键字来解决并发访问的安全问题,但这样太麻烦。

    Java5后专门提供了用来进行单变量多线程并发安全访问的工具包
    java.util.concurrent.atomic,其中的类也很简单。

    例如:
    AtomicLong aLong=new AtomicLong(10000); //原子量,每个线程都可以自由操作

    具体实现:

    Java中的Unsafe类提供了类似C++手动管理内存的能力。Unsafe类对普通程序员来说是危险的,一般应用开发者不会用到这个类。getIntVolatile方法用于在对象指定偏移地址处volatile读取一个int。putIntVolatile方法用于在对象指定偏移地址处volatile写入一个int。

    原子量实现的计数器

    1. //自定义计数器
    2. public class AtomicCounter {
    3. private static AtomicCounter instance = new AtomicCounter();
    4. private AtomicCounter() {
    5. }
    6. public static AtomicCounter getIntance() {
    7. return instance;
    8. }
    9. // 存储的数据,同时利用CAS提供线程安全的特性
    10. private AtomicInteger counter = new AtomicInteger();
    11. public int getCounter() {
    12. return counter.get();
    13. }
    14. public int increase() {
    15. // 内部使用死循环for(;;)调用compareAndSet(current, next)
    16. return counter.incrementAndGet();
    17. // return counter.getAndIncrement();
    18. }
    19. public int increase(int i) {
    20. // 内部使用死循环for(;;)调用compareAndSet(current, next)
    21. return counter.addAndGet(i);
    22. // return counter.getAndAdd(i);
    23. }
    24. public int decrease() {
    25. // 内部使用死循环调用compareAndSet(current, next)
    26. return counter.decrementAndGet();
    27. // return counter.getAndDecrement();
    28. }
    29. public int decrease(int i) {
    30. // 内部使用死循环调用compareAndSet(current, next)
    31. return counter.addAndGet(-i);
    32. // return counter.getAndAdd(-i);
    33. }
    34. public static void main(String[] args) throws Exception {
    35. final AtomicCounter ac = AtomicCounter.getIntance();
    36. // 可缓存线程池
    37. ExecutorService service = Executors.newCachedThreadPool();
    38. Set set = new CopyOnWriteArraySet<>();
    39. for (int i = 0; i < 1000; i++) {
    40. service.execute(new Runnable() {
    41. public void run() {
    42. int rr=ac.increase();
    43. set.add(rr);
    44. System.out.println(Thread.currentThread() + "::" + rr);
    45. }
    46. });
    47. }
    48. Thread.currentThread().sleep(2000);
    49. System.out.println(set.size());
    50. service.shutdown();
    51. }
    52. }

    CAS模型的问题

    1、ABA问题

    解决方法:JAVA中提供了AtomicStampedReference/AtomicMarkableReference来处理会发生ABA问题的场景,主要是在对象中额外再增加一个标记来标识对象是否有过变更。

     2、CAS应用场景隐含竞争是短暂的,否则不断的自旋尝试会过度消耗CPU

    解决方法加入超时设置

    3、CAS只能保证一个共享变量的原子操作,解决方法是使用锁或者合并多个变量

     AtomicReference提供了以无锁方式访问共享资源的能力 

     总结:

    AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference这些原子类型,它们无一例外都采用了基于volatile 关键字 +CAS 算法无锁的操作方式来确保共享数据在多线程操作下的线程安全性。
    - volatile关键字保证了线程间的可见性,当某线程操作了被volatile关键字修饰的变量,其他线程可以立即看到该共享变量的变化。
    - CAS算法,即对比交换算法,是由UNSAFE提供的,实质上是通过操作CPU指令来得到保证的。

    CAS算法提供了一种快速失败的方式,当某线程修改已经被改变的数据时会快速失败。
    - 当CAS算法对共享数据操作失败时,因为有自旋算法的加持,对共享数据的更新终究会得到计算。
    总之,原子类型用自旋+CAS的无锁操作保证了共享变量的线程安全性和原子性。

  • 相关阅读:
    异地容灾系统和数据仓库系统设计和体系结构
    前缀树(Trie):理解基本性质与应用
    操作系统 OS
    API接口随心搭,自由定制你的数据流
    一文图解爬虫_姊妹篇(spider)
    自定义注解实现参数验证和异常处理
    Django 入门学习总结7-静态文件管理
    day14学习总结
    【MySQL】表复制,去重,合并查询
    HTML5的学习
  • 原文地址:https://blog.csdn.net/qq_51222096/article/details/126841780