java---JUC并发包详解
目录
前言
一、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 {}}}
}
总结
加油哦~~
java---JUC并发包详解相关推荐
- Java JUC并发编程详解
Java JUC并发编程详解 1. JUC概述 1.1 JUC简介 1.2 进程与线程 1.2 并发与并行 1.3 用户线程和守护线程 2. Lock接口 2.1 Synchronized 2.2 什 ...
- Java JUC学习 - ConcurrentLinkedDeque 详解
Java JUC学习 - ConcurrentLinkedDeque 详解 0x00 前言 如何实现并发程序,对于Java以及其他高级语言来说都是一件并不容易的事情.在大一上学期的时候,我们学习了链表 ...
- 你真的弄明白了吗?Java并发之AQS详解
你真的弄明白了吗?Java并发之AQS详解 带着问题阅读 1.什么是AQS,它有什么作用,核心思想是什么 2.AQS中的独占锁和共享锁原理是什么,AQS提供的锁机制是公平锁还是非公平锁 3.AQS在J ...
- 【java】java ReentrantLock 源码详解
文章目录 1.概述 2.问题 3.ReentrantLock源码分析 3.1 类的继承关系 3.2 类的内部类 3.2.1 Sync类 3.2.2 NonfairSync类 3.2.3 FairSyn ...
- Java基础:volatile详解
Java基础:volatile详解 1.volatile保证可见性 1.1.什么是JMM模型? 1.2.volatile保证可见性的代码验证 1.2.1.无可见性代码验证 1.2.1.volatile ...
- Java 并发之 AQS 详解(上)
Java 并发之 AQS 详解 前言 Java SDK 为什么要设计 Lock 死锁问题 synchronized 的局限性 显式锁 Lock Lock 使用范式 Lock 是怎样起到锁的作用呢? 队 ...
- Java线程池ThreadPool详解
Java线程池ThreadPool详解 1. 线程池概述 1.1 线程池简介 1.2 线程池特点 1.3 线程池解决问题 2. 线程池原理分析 2.1 线程池总体设计 2.6 线程池流转状态 2.2 ...
- Java单元测试之JUnit4详解
2019独角兽企业重金招聘Python工程师标准>>> Java单元测试之JUnit4详解 与JUnit3不同,JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @B ...
- java -jar 和 -cp详解
java -jar 和 -cp详解 命令行执行程序 假如我们有一个程序,把它打包成Test.jar,如何运行才能成功输出Hello World package com.test; public cla ...
- java访问修饰符详解——学java,零基础不怕,不只要理论,更要实践+项目,a href=http://www.bjweixin.com太原维信科技提供 /a...
java访问修饰符详解--学java,零基础不怕,不只要理论,更要实践+项目 <a href=http://www.bjweixin.com>太原维信科技提供 </a> pub ...
最新文章
- 解决ScrollView嵌套ListView高度的问题
- 阿里研发效能数据知多少
- 重构 - 美股行情系统APP推送改造
- 华为厉害了:已启动6G网络技术研究
- shell grep cut 【整理】
- C程序设计语言现代方法05:选择语句
- 树莓派 USB摄像头 实现网络监控
- 2021-07-10蓝桥杯单片机学习知识点总结
- [slove]Unable to find required classes (javax.activation.DataHandler and javax.m
- 山东大学软件工程期末复习知识点总结
- 怎样在左边增加中国农历?
- 广西大学计算机学院导师张振荣,广西大学硕士研究生导师简介-张振荣
- CentOS7.9 通过 kubeadm1.23.5 安装 K8S
- sql server2000 安装时出现“另一个安装程序实例已在运行”的解决办法
- VueRouter导入
- AI公司CEO冒充中科大少年班校友!清华、斯坦福名校经历全造假
- 资源分享 | 仅需一个微软账号即可每天白嫖两小时Microsoft学习实验虚拟机云电脑...
- java 小鸡_小鸡快跳java
- unicode转中文的代码html,unicode的html页面编码转换成中文
- curve25519 - 数学的魅力
热门文章
- 教你如何几行python代码实现图片转手绘
- 纪念一下我那块分区表坏了的60G硬盘
- [WP/ctfshow/XXE]ctfshow_XXE_web373-378
- 一种基于计算机网络的流媒体,基于网络计算机的流媒体播放器的研究与实现.pdf...
- 【人民币识别】人民币序列号识别【含GUI Matlab源码 908期】
- 伯努利数(Bernoulli number)
- 树上倍增法求最近公共祖先LCA
- Sql Server 数据库 表增加列字段
- 色值的计算、转换、获取
- python输入年月日输出_python下输出指定年月日的方法之一