JAVA并发编程(2)——(如何保证原子性,原子类,CAS乐观锁,JUC常用类)
创始人
2025-05-30 08:01:44
0

如何保证原子性?

    • 如何保证原子性?
      • 4.1 锁
      • 4.2 JUC--原子变量
    • 原子类
    • CAS
    • JUC 常用类
      • 7.1 ConcurrentHashMap
      • 7.2 CopyOnWriteArrayList和CopyOnWriteSet
      • 7.3 辅助类 CountDownLatch
      • 7.4 辅助类 CyclicBarrier

如何保证原子性?

4.1 锁

锁是一种通用的技术方案,Java 语言提供的 synchronized 关键字,就是锁的 一种实现

在这里插入图片描述

synchronized 是独占锁/排他锁(就是有你没我的意思),但是注意! synchronized 并不能改变 CPU 时间片切换的特点,只是当其他线程要访问这个 资源时,发现锁还未释放,所以只能在外面等待。

synchronized 一定能保证原子性,因为被 synchronized 修饰某段代码 后,无论是单核 CPU 还是多核 CPU,只有一个线程能够执行该代码,所以一 定能保证原子操作.

synchronized 也能够保证可见性和有序性

4.2 JUC–原子变量

​ 现在我们已经知道互斥锁可以保证原子性,也知道了如何使用 synchronized 来保证原子性。但synchronized 并不是 JAVA 中唯一能保证原 子性的方案。

​ 如果你粗略的看一下 J.U.C(java.util.concurrent 包),那么你可以很显眼的 发现它俩:
在这里插入图片描述

一个是 locks 包,一个是 atomic 包,它们可以解决原子性问题。

加锁是一种阻塞式方式实现

原子变量是非阻塞式方式实现

原子类

原子类原理(AtomicInteger 为例)

原子类的原子性是通过 volatile + CAS 实现原子操作的。

java.util.concurrent 包下AtomicInteger 类中的 value 是有 volatile 关键字修饰的,这就保证了 value的内存可见性,这为后续的 CAS 实现提供了基础。

低并发情况下:使用 AtomicInteger。

getAndIncrement(); 代替 i++ 是安全的

代码实例:

package concurrentProgrammer.automicdemo;import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;public class ThreadDemo implements Runnable{//private  int num = 0;//共享变量// private volatile int num = 0;private AtomicInteger num = new AtomicInteger(0);@Overridepublic void run() {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+":::"+getNum());}public int getNum() {// return num++;return num.getAndIncrement();}}

测试:

package concurrentProgrammer.automicdemo;import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;public class Test {public static void main(String[] args) {ThreadDemo td = new ThreadDemo();for (int i = 0; i <10 ; i++) {//循环创建10个线程Thread t = new Thread(td);t.start();}}
}

结果:

在这里插入图片描述

没有重复证明了原子类的原子性

不用AtomicInteger的结果:

在这里插入图片描述

在这里插入图片描述

有重复没有遵从原子性

CAS

CAS(Compare-And-Swap) :比较并交换,该算法是硬件对于并发操作的支持

CAS 是乐观锁的一种实现方式,他采用的是自旋锁的思想,是一种轻量级的锁机制

即每次判断我的预期值和内存中的值是不是相同,如果不相同则说明该内存值 已经被其他线程更新过了,因此需要拿到该最新值作为预期值,重新判断。而该 线程不断的循环判断是否该内存值已经被其他线程更新过了,这就是==自旋的思想(在其他程序编写中也可以运用这一思想解决问题)==

CAS 包含了三个操作数:

①内存值 V

②预估值 A (比较时,从内存中再次读到的值)

③更新值 B (更新后的值)

当且仅当预期值 A==V,将内存值 V=B,否则什么都不做。

这种做法的效率高于加锁,当判断不成功不能更新值时,不会阻塞,继续获得 cpu 执行权,继续判断执行

在这里插入图片描述

CAS 的缺点

​ **1.高并发占用内存太高:**CAS 使用自旋锁的方式,由于该锁会不断循环判断,因此不会类似 synchronize

线程阻塞导致线程切换。但是不断的自旋,会导致 CPU 的消耗,在并发量大的 时候容易导致 CPU 跑满。

2.ABA问题

​ ABA 问题,即某个线程将内存值由 A 改为了 B,再由 B 改为了 A。当另外一个 线程使用预期值去判断时,预期值与内存值相同,误以为该变量没有被修改过而导致的问题

解决 ABA 问题的主要方式,通过使用类似添加版本号的方式,来避免 ABA 问题。 如原先的内存值为(A,1),线程将(A,1)修改为了(B,2),再由(B,2) 修改为(A,3)。此时另一个线程使用预期值(A,1)与内存值(A,3)进行 比较,只需要比较版本号 1 和 3,即可发现该内存中的数据被更新过了

JUC 常用类

Java 5.0 在 java.utilconcurrent 包中提供了多种并发容器类来改进同步容 器的性能

7.1 ConcurrentHashMap

​ ConcurrentHashMap 同步容器类是 Java 5 增加的一个线程安全的哈希 表对与多线程的操作,介于 HashMap 与 Hashtable 之间内部采用“锁分段”机制(jdk8 弃用了分段锁,使用 cas+synchronized)替代 Hashtable 的独 占锁。进而提高性能

放弃分段锁的原因:

  • 加入多个分段锁浪费内存空间。
  • 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待

jdk8 放弃了分段锁而是用了 Node 锁,减低锁的粒度,提高性能

并使用 CAS 操作来确保 Node 的一些操作的原子性,取代了锁。

put 时首先通过 hash 找到对应链表过后,查看是否是第一个 Node,

如果是, 直接用 cas 原则插入,无需加锁然后, 如果不是链表第一个 Node, 则直接用链表第一个 Node 加锁,这里加 的锁是 synchronized。

在这里插入图片描述

public class HashMapDemo {/*HashMap是线程不安全的,不能并发操作的ConcurrentModificationException  并发修改异常   遍历集合,并删除集合中的数据Hashtable 是线程安全的 public synchronized V put(K key, V value)-->独占锁锁直接加到了put方法上,锁粒度比较大,效率比较低用在低并发情况下可以Map map = Collections.synchronizedMap(new HashMap<>());直接获得一把线程安全的锁ConcurrentHashMap*/public static void main(String[] args) {ConcurrentHashMap map = new ConcurrentHashMap<>();//模拟多个线程对其操作for (int i = 0; i < 20; i++) {new Thread(()->{map.put(Thread.currentThread().getName(), new Random().nextInt());System.out.println(map);}).start();}}
}

7.2 CopyOnWriteArrayList和CopyOnWriteSet

ArrayList 是线程不安全的,在高并发情况下可能会出现问题, Vector 是线 程安全的. 但是在很多应用场景中,读操作可能会远远大于写操作。由于读操作根本不会修 改原有的数据,因此如果每次读取都进行加锁操作,其实是一种资源浪费我们 应该允许多个线程同时访问 List 的内部数据,毕竟读操作是线程安全的

JDK 中提供了 CopyOnWriteArrayList 类,将读取的性能发挥到极致,取是完 全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作,只有写入和写入之 间需要进行同步等待,读操作的性能得到大幅度提升。 CopyOnWriteArrayList 类的所有可变操作(add,set 等等)都是通过创建底 层数组的新副本来实现的当 List 需要被修改的时候,并不直接修改原有数组 对象,而是对原有数据进行一次拷贝,将修改的内容写入副本中。写完之后,再 将修改完的副本替换成原来的数据,这样就可以保证写操作不会影响读操作了。

CopyOnWriteArrayList在add方法中加了lock锁,又因为它的复制机制所以可以保证写和读不相互影响

在这里插入图片描述

CopyOnWriteArraySet 的实现基于 CopyOnWriteArrayList,不能存储重复数据.

CopyOnWriteArrayList和CopyOnWriteSet区别在于:

在这里插入图片描述

7.3 辅助类 CountDownLatch

CountDownLatch 这个类使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完 毕后,计数器的值就-1,当计数器的值为 0 时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。

package juc;import java.util.concurrent.CountDownLatch;public class CountDownLatchDemo {/*CountDownLatch 辅助类  递减计数器使一个线程 等待其他线程执行结束后再执行相当于一个线程计数器,是一个递减的计数器先指定一个数量,当有一个线程执行结束后就减一 直到为0 关闭计数器这样线程就可以执行了*/public static void main(String[] args) throws InterruptedException {CountDownLatch downLatch = new CountDownLatch(6);//计数for (int i = 0; i <6 ; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName());downLatch.countDown();//计数器减一操作}).start();}downLatch.await();//关闭计数System.out.println("main线程执行");}
}

结果:

在这里插入图片描述

7.4 辅助类 CyclicBarrier

CyclicBarrier 是一个同步辅助类,让一组线程到达一个屏障时被阻塞,直到最 后一个线程到达屏障时,屏障才会开门

package juc;import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierDemo {/*CyclicBarrier 让一组线程到达一个屏障时被阻塞,直到最 后一个线程到达屏障时,屏障才会开门是一个加法计数器,当线程数量到达指定数量时,开门放行*/public static void main(String[] args) {CyclicBarrier c = new CyclicBarrier(5, ()->{System.out.println("大家都到齐了 该我执行了");});for (int i = 0; i < 5; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName());try {c.await();//加一计数器} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}).start();}}
}

执行结果:

在这里插入图片描述

相关内容

热门资讯

描写清晨的句子 描写清晨的句子50则  导语:早起的云雀在那半明半暗的云空高啭着歌喉,而在遥远的遥远的天际,则有着一...
关于梦想的唯美句子   梦想,是对未来的一种期望,指在现实想未来的事或是可以达到但必须努力才可以达到的境况。每个人都有梦...
描写冬天的景色的句子 描写冬天的景色的句子(精选50句)  无论是身处学校还是步入社会,大家都收藏过令自己印象深刻的句子吧...
读书的句子 关于读书的句子书籍是巨大的力量——列宁书籍是青年人不可分离的生命伴侣和导师——高尔基读一本好书,就是...
[JAVA]类和对象 目录 1.类 1.1类的初步认识  1.2类的定义格式 1.3类的实例化 1.4类和对象的说明 2...
spring中各种现象的解释 1、现象一:实现了BeanFactoryPostProcessor的类无法解析@...
感人的爱情语句 感人的爱情语句20句  1、如果你也爱一个人,请不要去和别人暧昧,因为那样会伤害到你们之间得来不易的...
四月再见五月你好的句子 四月再见五月你好的句子(精选192句)  在日复一日的学习、工作或生活中,大家都听说过或者使用过一些...
早安共勉句子短信摘录 2020年早安共勉句子短信摘录60句  当我怀疑一切事物的存在时,我却不用怀疑我本身的思想,正因此时...
简单生活心态的句子 简单生活心态的句子(精选240句)  在我们平凡的日常里,大家肯定对各类句子都很熟悉吧,句子由词或词...
常见算法(一) 目录:  (1)基本查找 (2)...
货拉拉搬家须知 Q:需不需要提前预约。 A:不需要,我是当天预约ÿ...
哄女朋友开心的句子 哄女朋友开心的句子大全  无论是在学校还是在社会中,大家都收藏过令自己印象深刻的句子吧,句子可分为单...
基于深度学习的动物识别系统(Y... 摘要:动物识别系统用于识别和统计常见动物数量,通过深度学习技术检测日常几...
鸟的诗句摘抄 关于鸟的诗句摘抄  诗句就是组成诗词的句子。诗句通常按照诗文的格式体例,限定每句字数的多少。下面是小...
伤感语句 伤感语句30条  1、世界上最可怕的词不是分离,而是距离。  2、哭久了会累,也只是别人的以为。  ...
形容下雪的句子 形容下雪的句子(精选66句)  在学习、工作或生活中,大家对句子都再熟悉不过了吧,从句法角度说,句子...
计算机网络体系结构——“计算机... 各位CSDN的uu们你们好呀,今天小雅兰来学习一个全新的知识点,就是计算...
【C++学习】模板进阶——非类... 🐱作者:一只大喵咪1201 🐱专栏:《C...
【C语言】3天速刷C语言(指针... 字符指针在指针类型中我们知道有一种指针类型为字符指针char*;一般使用:...