java---JUC并发包详解

article/2025/10/1 9:28:45

目录

前言

一、atomic包

AtomicInteger类

AtomicReference类

AtomicStampedReference类

二、locks包

接口

Condition

Lock 

ReadWriteLock

实现类

ReentrantLock类

ReentrantReadWriteLock类

三、CountDownLatch

四、Semaphore(信号量)

总结


前言

JUC是java.util.concurrent包的简称,在Java5.0添加,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题!


一、atomic包

方便程序员在多线程环境下,无锁的进行原子操作。Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作。

AtomicInteger类

举个例子就是我们平时的  i++,不是原子的操作,在多线程环境下会发生错误,但是使用atomic提供的原子类,就可以很好的避免这个问题发生。

代码演示:

public class Test {static  AtomicInteger atomicInteger  = new AtomicInteger(0);static int count = 100000000;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{for(int i = 0; i < count; i++){//相当于i++atomicInteger.getAndIncrement();}});t.start();Thread t2 = new Thread(() -> {for(int i = 0; i < count; i++){//相当于i--atomicInteger.getAndDecrement();}});t2.start();t.join();t2.join();System.out.println(atomicInteger);}
}

运行结果

可以看到在一亿这个数量级上都是0,足以说明了原子性得到了保持。

AtomicReference类

CAS(关于CAS之前的博客有详细的介绍) 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。所以将多个变量封装成对象,通过AtomicReference来保证原子性。

AtomicReference类内部对变量的表示:

 private volatile V value;

AtomicStampedReference类

由于使用CAS会遇到ABA问题(关于ABA问题),所以使用AtomicStampedReference类实现了用版本号作比较的CAS机制。

AtomicStampedReference类内部对变量的表示

  private static class Pair<T> {final T reference;final int stamp;private Pair(T reference, int stamp) {this.reference = reference;this.stamp = stamp;}static <T> Pair<T> of(T reference, int stamp) {return new Pair<T>(reference, stamp);}}  private volatile Pair<V> pair;/*** Creates a new {@code AtomicStampedReference} with the given* initial values.** @param initialRef the initial reference   //变量的引用* @param initialStamp the initial stamp     //这个就是版本号*/public AtomicStampedReference(V initialRef, int initialStamp) {pair = Pair.of(initialRef, initialStamp);}

二、locks包

我们选出几个比较重要接口和实现类讲一下:

接口

上面可以看到一共有三个接口分别是 Condition、Lock、ReadWriteLock。

Condition

condition主要是为了在JUC框架中提供和Java传统的监视器风格的wait,notify和notifyAll方法类似的功能。condition一般和lock一起使用,就像synchronized与wait、notify使用一样

代码展示:

public class Test {static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {lock.lock();try {condition.await();System.out.println("子线程醒了");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});t.start();System.out.println("两秒之后叫醒子线程");Thread.sleep(2000);lock.lock();condition.signal();lock.unlock();}
}

运行结果

Lock 

在 Lock 接口中,获取锁的方法有 4 个:lock()、tryLock()、tryLock(long,TimeUnit)、lockInterruptibly(),为什么需要这么多方法?这些方法都有什么区别?接下来我们一起来看。

lock()

lock 方法是 Lock 接口中最基础的获取锁的方法,当有可用锁时会直接得到锁并立即返回,当没有可用锁时会一直等待,直到获取到锁为止,它的基础用法如下: 

Lock lock = new ReentrantLock();
// 获取锁
lock.lock();
try {// 执行业务代码...
} finally {//释放锁lock.unlock();   
}

lockInterruptibly() 

lockInterruptibly 方法和 lock 方法类似,当有可用锁时会直接得到锁并立即返回,如果没有可用锁会一直等待直到获取锁,但和 lock 方法不同,lockInterruptibly 方法在等待获取时,如果遇到线程中断会放弃获取锁。它的基础用法如下:

Lock lock = new ReentrantLock();
try {// 获取锁lock.lockInterruptibly();try {// 执行业务方法...} finally {// 释放锁lock.unlock();}
} catch (InterruptedException e) {e.printStackTrace();
}

使用 t.interrupt() 方法可以中断线程执行。 

tryLock() 

与前面的两个方法不同,使用无参的 tryLock 方法会尝试获取锁,并立即返回获取锁的结果(true 或 false),如果有可用锁返回 true,并得到此锁,如果没有可用锁会立即返回 false。它的基础用法如下:

Lock lock = new ReentrantLock();
// 获取锁
boolean result = lock.tryLock();
if (result) {try {// 获取锁成功,执行业务代码...} finally {// 释放锁lock.unlock();}
} else {// 执行获取锁失败的业务代码...
}

tryLock(long,TimeUnit)  

有参数的 tryLock(long,TimeUnit) 方法需要设置两个参数,第一个参数是 long 类型的超时时间,第二个参数是对参数一的时间类型描述(比如第一参数是 3,那么它究竟是 3 秒还是 3 分钟,是第二个参数说了算的)。在这段时间内如果获取到可用的锁了就返回 true,如果在定义的时间内,没有得到锁就会返回 false它的基础用法如下: 

Lock lock = new ReentrantLock();
try {// 获取锁(最多等待 3s,如果获取不到锁就返回 false)boolean result = lock.tryLock(3, TimeUnit.SECONDS);if (result) {try {// 获取锁成功,执行业务代码...} finally {// 释放锁lock.unlock();}} else {// 执行获取锁失败的业务代码...}
} catch (InterruptedException e) {e.printStackTrace();
}

synchronized和Lock的区别

1.synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。

2.synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。

3.通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

ReadWriteLock

首先ReentrantLock(后面介绍)某些时候有局限,如果使用ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。

因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

两个线程都是读:


public class Main {public static void main(String[] args) {ReadWriteLock readWriteLock = new ReentrantReadWriteLock();Lock readLock = readWriteLock.readLock();Lock writeLock = readWriteLock.writeLock();// “写”的角色,请求写锁// "只读“角色,请求读锁readLock.lock();        // 读锁已经有了// writeLock.lock();       // 写锁锁上Thread t = new Thread() {@Overridepublic void run() {readLock.lock();//      writeLock.lock();System.out.println("子线程也可以加锁成功");}};t.start();}
}

运行结果

一个线程读一个线程写 

public class Main {public static void main(String[] args) {ReadWriteLock readWriteLock = new ReentrantReadWriteLock();Lock readLock = readWriteLock.readLock();Lock writeLock = readWriteLock.writeLock();// “写”的角色,请求写锁// "只读“角色,请求读锁readLock.lock();        // 读锁已经有了// writeLock.lock();       // 写锁锁上Thread t = new Thread() {@Overridepublic void run() {//    readLock.lock();writeLock.lock();System.out.println("子线程也可以加锁成功");}};t.start();}
}

此时由于读锁还没有解锁,所以写锁会阻塞在哪里,不会有输出。 

实现类

ReentrantLock类

这个类前面我们已经用到了。首先根据字面意思表示可重入锁

什么是可重入锁:先看一下代码

public class Main {public static void main(String[] args) {// 我们是主线程Lock lock = new ReentrantLock();    // 名字已经说明,这把锁是可重入锁lock.lock();        // 锁已经被 main 线程锁了lock.lock();System.out.println("说明允许可重入");}
}

也就是是否允许持有锁的线程成功请求到同一把锁,这里得是同一个线程。 

对与synchrnized来说,也是可重入锁:代码如下:

public class Main2 {public static void main(String[] args) {Object lock = new Object();synchronized (lock) {   // main 线程已经对 lock 加锁了synchronized (lock) {   // main 线程再次对 lock 请求锁(处于已经锁上状态)System.out.println("这里打印了就说明了 sync 锁是可重入锁");}}}
}

ReentrantLock默认是不公平锁,但是可以修改。synchronized是不公平锁。

public class Main {public static void main(String[] args) {Lock lock = new ReentrantLock(true);    // fair = true:使用公平锁模式Lock lock1 = new ReentrantLock(false);    // fair = false:使用不公平锁模式Lock lock2 = new ReentrantLock();               // 默认情况下是不公平的}
}

Synchronized与ReentrantLock 

1.两者都是可重入锁

可重入锁:重入锁,也叫做递归锁,可重入锁指的是在一个线程中可以多次获取同一把锁,比如: 一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁, 两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

2.synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API

  • synchronized 是依赖于  JVM 实现的,虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的。
  • ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成)

3.ReentrantLock 比 synchronized 增加了一些高级功能

相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)

  • 等待可中断.通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
  • ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
  • ReentrantLock类线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”

4.使用选择

  • 除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。
  • synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

ReentrantReadWriteLock类

首先ReentrantLock某些时候有局限,如果使用ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。

因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。其实都是前面说的~~~。

三、CountDownLatch

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一(当然这里一个线程也可以返回多个)。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

代码演示:

public class Main {// count: 计数器为 3 个,只有 3 个全部报到了,门闩才会打开static CountDownLatch countDownLatch = new CountDownLatch(3);static class MyThread extends Thread {@Overridepublic void run() {countDownLatch.countDown();countDownLatch.countDown();countDownLatch.countDown();}}public static void main(String[] args) throws InterruptedException {MyThread t = new MyThread();t.start();countDownLatch.await();System.out.println("门闩被打开了");}
}

四、Semaphore(信号量)

Semaphore也叫信号量,在JDK1.5被引入,可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

Semaphore内部维护了一组虚拟的许可,许可的数量可以通过构造函数的参数指定。

  • 访问特定资源前,必须使用acquire方法获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可。
  • 访问资源后,使用release释放许可。

Semaphore和ReentrantLock类似,获取许可有公平策略和非公平许可策略,默认情况下使用非公平策略。

Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。
假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore做流量控制。

代码展示:

public class SemaphoreTest {private static final int COUNT = 40;private static Executor executor = Executors.newFixedThreadPool(COUNT);private static Semaphore semaphore = new Semaphore(10);public static void main(String[] args) {for (int i=0; i< COUNT; i++) {executor.execute(new ThreadTest.Task());}}static class Task implements Runnable {@Overridepublic void run() {try {//读取文件操作semaphore.acquire();// 存数据过程semaphore.release();} catch (InterruptedException e) {e.printStackTrace();} finally {}}}
}


总结

加油哦~~


http://chatgpt.dhexx.cn/article/O2muDkSE.shtml

相关文章

Java多线程并发编程--Java并发包(JUC)

Java多线程并发–Java并发包&#xff08;JUC&#xff09; 前言 前一篇文章中&#xff0c;笔者已经介绍了Java多线程的一些基础知识&#xff0c;但是想要成为一名中高级Java程序员还必须懂得Java并发包&#xff08;JUC&#xff09;的知识点&#xff0c;而且JUC现在也是面试中必…

接口测试--apipost如何自定义header中的content-type

使用apipost进行接口测试的时候&#xff0c;有时候会用到一些自定义或者不常见的content-type格式&#xff0c;这个时候就要手动在header头部自定义content-type。 这里我们自定义一个content-type&#xff0c;格式为application/octet-stream 然后body选择的为form-data&…

Type-c引脚定义

Type-c口是什么口&#xff0c;有什么作用&#xff1f; Type-c口在大家的视野中或许比较陌生&#xff0c;但是生活中处处离不开Type-c口的存在。手机&#xff0c;电脑&#xff0c;音箱&#xff0c;小家电&#xff0c;无人机…等等&#xff0c;都存在Type-c接口。 Type-c只是一种…

TYPE-C12PIN接口电路

16PIN封装只有12个PIN脚&#xff0c;所有16/12PIN其实是一种规格 特点&#xff0c;正反插 VBS-VCC A5,B5接电阻拉地 A7,B7串联接电阻成为DN A6,B6串联接电阻成为DP GND拉地 外壳拉地 USB接口介绍

TypeScript-interface接口篇

简介 接口的作用&#xff1a;在面向对象的编程中&#xff0c;接口是一种规范的定义&#xff0c;它定义了行为和动作的规范&#xff0c;在程序设计里面&#xff0c;接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范&#xff0c;接口不关心这些类的内部状态数…

TypeScript(四)接口

目录 前言 定义 用法 基本用法 约定规则 属性控制 任意属性 可选属性 只读属性 定义函数 冒号定义 箭头定义 接口类型 函数接口 索引接口 继承接口 类接口 总结 前言 本文收录于TypeScript知识总结系列文章&#xff0c;欢迎指正&#xff01; 在介绍TS对象…

Type-C接口简单介绍-面向单片机应用

Type-C接口简单介绍-面向单片机应用 1、绪论 用单片机做一些东西时&#xff0c;Type-C接口逐渐替代了MicroUSB接口。但不像MicroUSB那样只有5V、GND、D、D-、ID五个接口&#xff0c;Type-C接口有24个引脚&#xff0c;比较复杂。大多时候我们用TypeC也用不到USB3.0协议&#x…

CTP_将C++封装为Python可调用接口

目录 写在前面&#xff1a; 前置准备&#xff1a; step 1 与上期所原始代码对比分析源码 td源码 1 配置属性-》常规-》配置类型 要为 “动态库(.dll)” 2 VC目录 -》包含目录 3 VC目录 -》 库目录 4 链接器-》常规-》附加库目录 5 链接器-》输入-》附加依赖项 vnctp.h 的功…

一文读懂USB Type-C接口 <一>:引脚和功能指南

本文将介绍USB Type-C标准的一些最重要的特性。 你知道如何使用USB Type-C接口吗?本文列出了USB Type-C引脚的解剖结构&#xff0c;并简要介绍了其各种模式。 USB Type-C是一种USB连接器系统的规范&#xff0c;它在智能手机和移动设备中越来越受欢迎&#xff0c;能够提供电力和…

VisionPro连接相机步骤

一、修改相机与电脑IP地址在同一网段上 1、修改相机IP地址 在菜单栏找到 “Cognex GigE Vision Configurator” &#xff0c;可直接输出搜索。 或者在visionPro默认安装目录下 “C:\Program Files\Cognex\VisionPro\bin”&#xff0c;找到“Cognex GigE Vision Configurator…

机器视觉——相机选型

目录 相机选型 分辨率、快门、帧率、色彩、靶面、接口 镜头选型 分辨率、靶面、焦距、接口、光圈畸变工作距离 常用计算示例 相机选型 分辨率、快门、帧率、色彩、靶面、接口 镜头选型 分辨率、靶面、焦距、接口、光圈畸变工作距离 常用计算示例 1. 面阵相机和镜头选型 已…

线扫相机的选择

1.通过幅宽和精度求像素个数选择相机&#xff1a; Rmin为最小像素数&#xff1b;FOV为检测幅宽&#xff1b;X为检测精度。通过计算结果选择相机大小。 如果幅宽要求120mm&#xff0c;精度要求0.1&#xff0c;得到最少所需像素个数为1200&#xff0c;选择2k的线扫相机即可满足。…

1-CCD相机选型

1-相机分类 2-以像素数选择&#xff08;高像素型或标准型&#xff09; 从“像素分辨率”这一点来添加良否判定的基准&#xff0c;可选择最佳像素数的相机&#xff01; 视觉系统所使用的 CCD 拍摄元件是以格子状排列的较小像素的集合体。在作为标准型经常使用的 31 万像素 CCD …

相机位姿估计

相机位姿估计 前言旋转角度欧拉角相机位姿求解旋转矩阵和旋转向量之间的转换旋转矩阵和欧拉角之间的转换平移量求解代码 前言 这部分内容博主也不是很熟悉&#xff0c;写下这篇博文想记录下自己当时的求解过程&#xff0c;也想让看到的朋友一起讨论&#xff0c;看看我做的对不…

iOS 相册多选 相机选择图片

前言 经过几天的断断续续的编写终于把这一个小项目完成了&#xff0c;现在刚刚完成&#xff0c;代码看着不整洁&#xff0c;请多包涵。 前几天要弄个相册多选和照相选图的功能&#xff0c;以前做过单选上传头像之类的。但是多选确实不像那么简单&#xff0c;github找了好多的…

工业相机和镜头选型技巧

工业相机和镜头选型技巧 一、加接圈&#xff0c;视野为什么会变小&#xff1f;1、视野公式理解2、加接圈后视野变小分析 二、在如下试验台中&#xff0c;加了接圈&#xff0c;图像要清晰&#xff0c;那么相机高度应该如何调整&#xff1f;1、试验台场景2、像距、物距和焦距的关…

工业视觉检测如何选择合适的工业相机?

1、根据应用的不同分别选用CCD或CMOS相机CCD工业相机主要应用在运动物体的图像提取&#xff0c;如贴片机机器视觉&#xff0c;当然随着CMOS技术的发展&#xff0c;许多贴片机也在选用CMOS工业相机。用在视觉自动检查的方案或行业中一般用CCD工业相机比较多。CMOS工业相机由成本…

【工业相机】【深度3】相机选择-精度和曝光需求计算 - 输入:1 被测试物体的最小体积 2 被测物体的移动相对速度

前言&#xff1a;本举例&#xff0c;说明&#xff0c;我们在工业场景下&#xff0c;如果需要在某个速度下计算某个尺寸的物体的工业相机的精度计算方法 1 需求定义 本需求定义为测量一个有移动速度的工业被测物体&#xff1a; 输入参数标识输入参数举例FOVFOV12寸&#xff08…

机器视觉-相机选择方法-缺陷检测

主要分为三部分 1.相机示意图及基本结构 2.相机参数确定方法 3.最终选择 1.相机示意图及基本结构 简图↑ 全图↑ 光圈与景深↑ 2.相机基本参数确定 视野与像素确定 被检测石英镜片的最大直径为38.6mm。也就是最大弥散圆直径。 样品↑ 较小划痕样本↑ 划痕测量↑ 如上图&…