【Java核心知识】JUC包相关知识

文章目录

  • JUC包主要内容
  • Java内置锁
    • 为什么会有线程安全问题
    • Synchronize锁
    • Java对象结构
    • Synchronize锁优化
    • 线程间通信
    • Synchronize与wait原理
  • CAS和JUC原子类
    • CAS原理
    • `JUC`原子类
    • `ABA`问题
  • 可见性和有序性
    • 为什么会有可见性
    • 参考链接
  • 显式锁
    • Lock接口常用方法
    • 显式锁分类
    • 显式锁实现原理
    • 参考链接

JUC包主要内容

JUC包是与并发编程相关的包,主要包含四部分原子类并发集合多线程,如下图所示。

其中,

  • 锁可以分为内置锁和显式锁;
  • 原子类主要是一些通过CAS实现的原子类;
  • 并发集合主要就是一些线程安全的集合,比如ConcurrentHashMap,BlockQueue等;
  • 多线程包括callable接口和线程池等;

在这里插入图片描述

Java内置锁

为什么会有线程安全问题

i++线程不安全的原因在于自增操作不是原子性的,可以分为三步:内存取值寄存器加1存值到内存

除了原子性之外,可见性有序性也会导致线程安全问题。可见性是指线程B并不一定能够及时看到线程A对变量的修改。

Synchronize锁

Synchronize关键字可以作用在方法上,也可以作用于代码块上,本质上都是锁住了某个对象,但synchronize作用于方法上是一种粗粒度的锁,会导致其他线程也不能访问该对象的其他方法。

JVM的堆中,每个对象都会有一个对象监视器,synchronize就是锁住了这个对象监视器,从而保证了原子性。

那么如何保证可见性呢?线程加锁时,必须清空工作内存中共享变量的值,从而使用共享变量时需要从主内存重新读取;线程在解锁时,需要把工作内存中最新的共享变量的值写入到主存,以此来保证共享变量的可见性。(这里是个泛指,不是说只有在退出synchronized时才同步变量到主存)

Java对象结构

Java的对象都放在JVM的堆中,每个对象的结构包括:

  • 对象头:

    • Mark Word:记录哈希码,GC标志位、锁状态等信息。不同锁状态下Mark Word是不同的,但最后两位都代表了锁状态。

    • 类对象指针:指向方法区的该类相关信息

    • 数组长度:如果对象是数组才有此结构

  • 对象体:包含对象的实例变量,包含父类的实例变量

  • 对齐字节:为了保证8字节的对齐而填充的数据

Synchronize锁优化

为了优化Synchronize锁的性能,Java提出了逐步升级的四种锁:无锁->偏向锁->轻量级锁->重量级锁。

  • 无锁:
  • 偏向锁:Mark Word中存储持有锁的线程ID,当有线程执行时,先判断对象头的线程ID是否与此线程ID相等,如果相等,直接向下执行;如果不相等,说明存在竞争,锁升级为轻量级锁。
  • 轻量级锁:对象头存储持有锁的线程ID,将对象头原来的哈希码放入线程栈帧中的锁记录中。当别的线程竞争锁时,不会立即阻塞,切换用户态,而是会自旋,然后使用CAS尝试获取锁,降低了阻塞线程的消耗。自旋等待时间和上一个竞争线程等待结果有关:如果上一个竞争线程自旋成功了,那么这次自旋的次数会更多;如果上一个竞争线程自旋失败了,那么这次自旋的次数会减少。自旋不会一直持续下去,如果超过了指定时间,会膨胀为重量级锁!
  • 重量级锁:重量级锁对象头会指向一个监视器对象(每个对象都有一个监视器对象),该监视器通过三个队列(竞争队列、阻塞队列、等待时间片的就绪队列)来登记和管理排队的线程,会涉及到线程的阻塞,切换用户态。

轻量级锁执行过程:

  • 1、判断对象是否加锁,如果没加锁,进行以下操作

  • 2、在自己的栈帧中创建锁记录,用来存放加锁对象的哈希码

  • 3、创建好锁记录后,通过CAS自旋操作,尝试将锁对象头的锁记录指针替换成栈帧中锁记录的地址

  • 4、替换栈帧后会返回锁对象的哈希码,然后填入栈帧的锁记录中

线程间通信

可以使用Objectwait(),notify()方法来进行线程间的通信。

wait()方法的原理

1)当线程调用了lock(某个同步锁对象)的wait()方法后,JVM会将当前线程加入lock监视器的WaitSet(等待集),等待被其他线程唤醒。
2)当前线程会释放lock对象监视器的Owner权利,让其他线程可以抢夺lock对象的监视器。
3)让当前线程等待,其状态变成WAITING

notify()方法的原理

1)当线程调用了lock(某个同步锁对象)的notify()方法后,JVM会唤醒lock监视器WaitSet中的第一个等待线程。
2)当线程调用了locknotifyAll()方法后,JVM会唤醒lock监视器WaitSet中的所有等待线程。
3)等待线程被唤醒后,会从监视器的WaitSet移动到EntryList,线程具备了排队抢夺监视器Owner权利的资格,其状态从WAITING变成BLOCKED
4)EntryList中的线程抢夺到监视器Owner权利之后,线程的状态从BLOCKED变成Runnable,具备重新执行的资格。

缓冲队列

/*** 生产者消费者队列*/
//数据缓冲区,类定义
public class DataBuffer<T> {public static final int MAX_AMOUNT = 10; //数据缓冲区最大长度//保存数据private List<T> dataList = new LinkedList<>();//数据缓冲区长度private Integer amount = 0;// 用来保证只有一个线程存元素或者取元素private final Object LOCK_OBJECT = new Object();// 当队列满了后,用于阻塞生产者private final Object NOT_FULL = new Object();// 当队列为空时,用于阻塞消费者private final Object NOT_EMPTY = new Object();// 向数据区增加一个元素public void add(T element) throws Exception{// 队列已满,不能存元素while (amount > MAX_AMOUNT){synchronized (NOT_FULL){System.out.println("队列已经满了!");// 等待未满通知,这里为什么需要wait,是因为需要等待一个条件满足,而不能只用synchronize,某一时刻只有一个线程拥有NOT_FULL是不行的NOT_FULL.wait();}}// 保证原子性synchronized (LOCK_OBJECT){dataList.add(element);amount++;System.out.println(Thread.currentThread().getName() + "生产了一条消息" + amount);}synchronized (NOT_EMPTY){//发送未空通知NOT_EMPTY.notify();}}/*** 从数据区取出一个商品*/public T fetch() throws Exception{// 数量为零,不能取元素while (amount <= 0){synchronized (NOT_EMPTY){System.out.println(Thread.currentThread().getName() + "队列已经空了!");//等待未空通知NOT_EMPTY.wait();}}T element = null;// 保证原子性synchronized (LOCK_OBJECT){element = dataList.remove(0);amount--;System.out.println(Thread.currentThread().getName() + "消费了一条消息" + amount);}synchronized (NOT_FULL){//发送未满通知NOT_FULL.notify();}return element;}
}

生产者和消费者

@Test
public void testProducerConsumerQueue() throws InterruptedException {//共享数据区,实例对象DataBuffer<String> dataBuffer = new DataBuffer<>();// 同时并发执行的线程数final int THREAD_TOTAL = 20;//线程池,用于多线程模拟测试ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_TOTAL);//假定共11条线程,其中有10个消费者,但是只有1个生产者final int CONSUMER_TOTAL = 10;final int PRODUCE_TOTAL = 1;for (int i = 0; i < PRODUCE_TOTAL; i++) {//生产者线程每生产一个商品,间隔50毫秒threadPool.submit(() -> {for (int j = 0; j < 10; j ++) {//首先生成一个随机的商品String s = "商品";//将商品加上共享数据区try {dataBuffer.add(s);} catch (Exception e) {throw new RuntimeException(e);}}});}for (int i = 0; i < CONSUMER_TOTAL; i++){//消费者线程每消费一个商品,间隔100毫秒threadPool.submit(() -> {for (int j = 0; j < 2; j ++) {// 从PetStore获取商品String s = null;try {s = dataBuffer.fetch();} catch (Exception e) {throw new RuntimeException(e);}}});}Thread.sleep(10000);
}

Synchronize与wait原理

Synchronizewait都会将线程加入到等待队列中,但是两者加入的等待队列并不是同一个,Synchronize加入的是对象监视器的等待队列,当退出Synchronize代码块后会自动唤醒线程,而waitObject的方法,加入的是另一个等待集合,只能通过notify()notifyAll()唤醒。

CAS和JUC原子类

CAS原理

CAS(Compare And Swap),是比较交换的缩写,可以用来实现乐观锁。乐观锁本质上是无锁的,每次更新前都把原来的旧值和要更新的新值一块传入,如果发现传入的旧值和当前内存上的旧值一样,则更新成功;否则更新失败;

乐观锁就是一直调用CAS操作,不断获取旧值,计算新值,然后传入旧值和新值进行更新,线程一直在自旋,直到更新成功为止。

示例

public class CompareAndSwap {public volatile int value; //值//不安全类// private static final Unsafe unsafe = Unsafe.getUnsafe();private static final Unsafe unsafe = getUnsafe();//value 的内存偏移(相对与对象头部的偏移,不是绝对偏移)private static final long valueOffset;//统计失败的次数public static final AtomicLong failure = new AtomicLong(0);static{try{//取得value属性的内存偏移valueOffset = unsafe.objectFieldOffset(CompareAndSwap.class.getDeclaredField("value"));System.out.println("valueOffset:=" + valueOffset);} catch (Exception ex) {throw new Error(ex);}}//通过CAS原子操作,进行“比较并交换”public final boolean unSafeCompareAndSet(int oldValue, int newValue){//原子操作:使用unsafe的“比较并交换”方法进行value属性的交换return unsafe.compareAndSwapInt( this, valueOffset, oldValue, newValue );}//使用无锁编程实现安全的自增方法public void selfPlus(){int oldValue = value;//通过CAS原子操作,如果操作失败就自旋,直到操作成功for(;;) {oldValue = value;failure.incrementAndGet();if (unSafeCompareAndSet(oldValue, oldValue + 1)) return;}// do// {//     // 获取旧值//     oldValue = value;//     //统计无效的自旋次数//     //记录失败的次数//     failure.incrementAndGet();// } while (!unSafeCompareAndSet(oldValue, oldValue + 1));}/*** 通过反射获取Unsafe* @return*/public static Unsafe getUnsafe(){try {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);return (Unsafe) theUnsafe.get(null);} catch (Exception e) {throw new AssertionError(e);}}
}
/*** 测试CAS操作* @throws InterruptedException*/
@Test
public void testCAS() throws InterruptedException {final CompareAndSwap compareAndSwap = new CompareAndSwap();AtomicInteger res = new AtomicInteger(0);//倒数闩,需要倒数THREAD_COUNT次CountDownLatch latch = new CountDownLatch(10);for (int i = 0; i < 10; i++){// 提交10个任务Executors.newCachedThreadPool().submit(() ->{//每个任务累加1000次for (int j = 0; j < 1000; j++){compareAndSwap.selfPlus();res.getAndIncrement();}latch.countDown();// 执行完一个任务,倒数闩减少一次});}latch.await();// 主线程等待倒数闩倒数完毕System.out.println(res);System.out.println("累加之和:" + compareAndSwap.value);System.out.println("失败次数:" + CompareAndSwap.failure.get());
}

JUC原子类

JUC包下的原子类可以分为四组:

  • 基本原子类:AtomicInteger,整型;AtomicLong,大整数;AtomicBoolean:布尔型;
  • 数组原子类:AtomicIntegerArray:整型数组原子类;AtomicLongArray:长整型数组原子类;AtomicReferenceArray:引用类型数组原子类。
  • 引用原子类:AtomicReference:引用类型原子类;AtomicMarkableReference:带有更新标记位的原子引用类型;AtomicStampedReference:带有更新版本号的原子引用类型。
  • 字段更新原子类:AtomicIntegerFieldUpdater:原子更新整型字段的更新器;AtomicLongFieldUpdater:原子更新长整型字段的更新器;AtomicReferenceFieldUpdater:原子更新引用类型里的字段。

JUC原子类下的底层实现也是通过不断CAS自旋+volatile(实现可见性)实现的,可以从源码看到。

ABA问题

使用CAS自旋更新虽然没有加锁,降低了线程切换成本,但是容易产生ABA问题。即线程1将值从A到B又到A,此时线程2被唤醒,以为变量没有改变过,从而引起错误的判断。解决办法是添加时间戳,可以借助AtomicStampedReference原子类实现。

可见性和有序性

为什么会有可见性

现代处理器都是多核的,每个核都会有自己独有的高速缓存L1,L2,L3,这些核又共享一个主内存,每次涉及变量更新或读取时,CPU都是先从高级缓存中读取并进行修改,然后随机写入到主存。这样就产生了问题,如果核1对公有变量A进行了修改,但是还没来得及写入主存,那么核2从主存中读取到的值就是未及时更新的脏值。

一般操作系统会使用Lock指令在总线上进行广播,哪些变量的高速缓存已失效,必须从主存中重新读取。Java的volatile关键字会在字节码上加入loadloadloadstorestorestorestoreload内存屏障来保证更改后的变量立即写入主存,且告知其他核的高速缓存该值已失效,必须从主内存重新读取。

volatile并不保证原子性,因为虽然volatile会强制将修改刷回主存,但是修改并刷回主存的指令不是原子性的,可能有中断的可能。比如线程A修改完变量后,准备刷回主存,这时发生了线程调度,线程B知道自己的数据失效了,但是从主存中重新获取的数据不一定是最新的,因为线程A只是在本地修改了数据,但还没有写入主存。

参考链接

内存屏障与JVM指令

如果你知道这灵魂拷问的6连击,面试volitile时就稳了

显式锁

Lock接口常用方法

所有的锁实现类都会实现Lock接口,该接口主要有以下几个方法:

  • lock():阻塞获取锁,如果当前线程不能抢到锁,线程会加入阻塞队列进行等待,直到获取到锁;

  • tryLock(): 非阻塞抢锁,如果当前线程抢不到锁,线程会立刻返回false

  • tryLock(long time, TimeUnit unit): 超时返回,如果当前线程在一段时间内抢不到锁,则会返回false

  • unlock(): 释放锁;

下面是一个使用ReentrantLock的示例,使用三个线程同时对某一个执行加一操作,每个线程操作100次,累计300次。

public class LockTest {private int count;@Testpublic void testReentrantLock() throws InterruptedException {Lock lock = new ReentrantLock();ExecutorService executorService = Executors.newCachedThreadPool();CountDownLatch countDownLatch = new CountDownLatch(3);for (int i = 0; i < 3; i ++) {executorService.execute(() -> {for (int j = 0; j < 100; j ++) {// 获取锁lock.lock();try {count ++;} finally {// 释放锁lock.unlock();}}// 每完成一个线程,就更新countDownLatchcountDownLatch.countDown();});}countDownLatch.await();System.out.println(count); }
}

显式锁分类

显式锁的分类有很多种,大致上可以分为下面这些:

在这里插入图片描述

显式锁实现原理

JUC包下的显式锁都是基于AQS实现的,AQS使用一个队列保存想要获取锁的线程,同时在队头使用CAS竞争获取锁,不会阻塞线程,是一种乐观锁

当有新线程加入时,会通过CAS加入队尾,然后监控队列前一个元素的状态,这时不会发生CAS,但线程也不会阻塞,而是会调用yield()主动让出时间片。

参考链接

Java中常见的各种锁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/118793.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gitlab-runner安装和部署项目

目录 1.安装gitlab-runner 1.1 添加官方仓库 1.2.1 安装最新版本 1.2.2 安装指定版本&#xff08;可选&#xff09; 1.2.3 更新runner&#xff08;可选&#xff09; 1.3 随便点开gitlab上的一个项目 1.4 gitlab-runner的注册 2.配置gitlab-runner 3.runner一些命令 gi…

Blender界面学习02

学习视频 【基础篇】1.3 认识界面_哔哩哔哩_bilibili 基本的3d建模的流程是什么&#xff1f; 四个角现出加号时可以拆分窗口&#xff0c;也可以合并窗口 向自己的方向拉是合并&#xff0c;向不是自己的方向拉是合并 如果界面搞乱后需要回到原来的布局 然后在新建的布局上右击 …

HTML5+CSS3+JS小实例:科技感满满的鼠标移动推开粒子特效

实例:科技感满满的鼠标移动推开粒子特效 技术栈:HTML+CSS+JS 效果: 源码: 【html】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport&qu…

2、[春秋云镜]CVE-2022-30887

文章目录 一、靶标介绍二、复现过程 一、靶标介绍 二、复现过程 &#xff08;1&#xff09;打开网址。 &#xff08;2&#xff09;查看源代码 邮件格式&#xff1a;第一个符号不准为&#xff0c;后续符号有、.&#xff1b; 密码格式&#xff1a;匹配所有小写字母&#xff0c…

2. postgresql并行扫描(1)——pg强制走并行扫描建表及参数配置

转载自&#xff1a;https://developer.aliyun.com/article/700370 1. 参数设置 1.1 postgresql.conf中修改 # 1、总的可开启的WORKER足够大 max_worker_processes 128# 2、所有会话同时执行并行计算的并行度足够大 max_parallel_workers64# 3、单个QUERY中并行计算NODE开…

编译KArchive在windows10下

使用QT6和VS2019编译KArchive的简要步骤&#xff1a; 安装 Qt &#xff0c;我是用源码自己编译的 "F:\qtbuild"安装CMakefile并配置环境变量安装Git下载ECM源码 https://github.com/KDE/extra-cmake-modules.git-------------------------------------------------…

C语言之练习题

欢迎来到我的&#xff1a;世界 希望作者的文章对你有所帮助&#xff0c;有不足的地方还请指正&#xff0c;大家一起学习交流 ! 目录 前言简答题第一题第二题 编程题第一题第二题第三题第四题 总结 前言 这期文章由&#xff1a;两题问答题四道编程题&#xff1b;小孩在文章中写…

远程访问Linux的DataEase数据可视化分析,有哪些推荐的工具?

DataEase 是开源的数据可视化分析工具&#xff0c;帮助用户快速分析数据并洞察业务趋势&#xff0c;从而实现业务的改进与优化。是开源的数据可视化分析工具&#xff0c;帮助用户快速分析数据并洞察业务趋势&#xff0c;从而实现业务的改进与优化。 在本地搭建后,借助cpolar 内…

r 安装源码包 安装本地r包

总结一下手动安装R包 - 简书 (jianshu.com)https://www.jianshu.com/p/2a7a36414734 #BiocManager::install("simplifyEnrichment") #BiocManager::install("EnsDb.Hsapiens.v86")#下载包 之后 手动安装 #install.packages("~/datasets/EnsDb.Hsapien…

Unity 粒子特效遮罩(ParticleMask)

1.需求&#xff1a; 游戏中粒子特效能实现非常好的效果&#xff0c;但是由于粒子特效是独立的系统&#xff0c;Unity自带的Mask普通的遮罩&#xff0c;遮不住粒子特效。 2.实现原理&#xff1a; 通过shader把超出范围的粒子纹理(Texture)&#xff0c;改成透明颜色&#xff0…

opencv android sdk 使用中的问题

Plugin with id ‘kotlin-android’ not found 在build.gradle(:app)中添加以下内容 buildscript {ext {Kotlin_Verion "1.9.10"}dependencies {classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$Kotlin_Verion"}repositories {mavenCentral()} …

stm32----SPI协议

一、概述 SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外围设备接口&#xff09;&#xff0c;是Motorola公司提出的一种同步串行接口技术&#xff0c;是一种高速、全双工、同步通信总线&#xff0c;在芯片中只占用四根管脚用来控制及数据传输&#xff0c;节约…

docker linux(centos 7) 安装

这是个目录 1:安装1:手动安装(适用于centos7)之一2:手动安装(适用于centos7)之二3&#xff1a;一键安装docker4:二进制安装1&#xff1a;下载二进制包2&#xff1a;解压3&#xff1a;移动文件4&#xff1a;后台运行docker5&#xff1a;测试 dicker命令表999&#xff1a;遇到的问…

华为数通方向HCIP-DataCom H12-821题库(单选题:181-200)

第181题 某管理员需要创建AS Path过滤器(ip as-path-iter),允许AS_Path中包含65001的路由通过,那么以下哪一项配置是正确的? A、​​ip as-path-filter 1 permit 65001​​ B、​​ip as-path-filter 1 permit "65001​​ C、​​ip as-path-filter 1 permit *6500…

2023高教社杯数学建模思路 - 案例:ID3-决策树分类算法

文章目录 0 赛题思路1 算法介绍2 FP树表示法3 构建FP树4 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模…

2023开学礼《乡村振兴战略下传统村落文化旅游设计》许少辉新财经理工 ​​​

2023开学礼《乡村振兴战略下传统村落文化旅游设计》许少辉新财经理工 ​​​

如何高性能、高效率地实现3D Web轻量化?

随着互联网和Web技术的发展&#xff0c;3D Web应用的需求越来越多。然而&#xff0c;复杂的3D模型在Web上展示 和交互通常需要大量的带宽和计算资源。 为了解决这一问题&#xff0c;HOOPS技术作为一套专业的3D图形技术开发工具包&#xff0c;发挥着关键作用。本文 将探讨HOO…

12. 自动化项目实战

目录 1. 登录测试 2. 测试首页的帖子列表数不为0 3. 帖子详情页校验 4. 发布帖子 5. 退出登录 自动化项目实施的基本流程如下图所示&#xff1a; 手工测试用例、自动化测试用例。 1. 登录测试 校验登录后主页显示的用户名称和登录时输入的用户名是否相等。 public class…

java八股文面试[数据库]——慢查询优化

分析慢查询日志 直接分析慢查询日志&#xff0c; mysql使用explain sql语句进行模拟优化器来执行分析。 oracle使用explain plan for sql语句进行模拟优化器来执行分析。 table | type | possible_keys | key |key_len | ref | rows | Extra EXPLAIN列的解释&#xff1a; ta…

Java应用CPU占用过高故障排除

一、背景 最近测试反馈测试环境接口偶现有访问超时&#xff0c;然后APP提示是网络失败&#xff0c;看了一下测试环境的应用完全没啥问题&#xff0c;一直以为是网络问题。 今天测试有反馈了&#xff0c;赶紧看了一下测试服务器&#xff0c;这次终于有症状了&#xff0c;CPU直…