一. JUC介绍
1.1 基本概念
从Java 5开始,在JDK中多出了java.util.concurrent包(简称:JUC)。
JUC主要是让开发者在多线程编程中更加简单、方便一些。通过JDK内置了一些类、接口、关键字,补充完善了JDK对于并发编程支持的“短板”。
1.2 JUC中的类和接口
之前在多线程中学习的Callable和Future属于JUC的内容
通过IDEA可以看java.util.concurrent包中所有的类和接口
1. 找到java.util.concurrent包
2. 选中concurrent包, 右键
3. 选择Diagrams
4. 选择Show Diagram
1.3主要包含功能
Executor:线程池
Atomic:原子操作类
Lock:锁
Tools:信号量工具类
并发集合:提供了线程安全的集合类。
二. 线程池
2.1 为什么使用线程池
线程的创建和线程的销毁都会消耗一定的系统资源。
如果需要频繁的新建线程、销毁线程,对于系统可能需要消耗大量资源。
如何在需要频繁新建、销毁线程的场景下保证系统响应时间更快,消耗资源更少?
答:使用线程池
2.2 什么是线程池
内存中的一块空间。这块空间里面存放一些已经实例化好的线程对象。
当代码中需要使用线程时直接从线程池获取。当代码中线程执行结束或需要销毁时,把线程重新放入回到线程池,而不是让线程处于死亡状态。
2.3 使用线程池的特点
2.3.1 优点
降低系统资源消耗。通过重用线程对象,降低因为新建和销毁线程产生的系统消耗。
2. 提高系统响应速度。直接从内存中获取线程对象,比新建线程更快。
提供线程可管理性。通过对线程池中线程数量的限制,避免无限创建线程导致的内存溢出或CPU资源耗尽等问题2.3.2 缺点
默认情况下,无论是否需要使用线程对象,线程池中都有一些线程对象,也就是说会占用一定内存
三. JUC中的线程池
3.1 Executor介绍
Executor 线程池顶级接口
接口中只有一个execute()方法,方法参数为Runnable对象3.2 Executor关系图
3.3 ThreadPoolExecutor
3.3.1介绍
ThreadPoolExecutor是JUC中提供的默认线程池实现类
3.3.2 构造方法
提供了四种参数列表的构造方法
其中包含7个参数的构造方法, 这是ThreadPoolExecutor支持的所有参数3.3.3 参数详解
1. corePoolSize 核心线程数
创建线程池后,默认线程池中并没有任何的线程,执行了从线程池获取线程的时候才会创建核心线程并返回. 如果没有达到指定的corePoolSize, 即使有空闲的核心线程, 也会创建新的核心线程执并返回, 直到达到了corePoolSize. 达到corePoolSize后, 从线程池获取线程, 有空闲的核心线程, 就返回空闲的线程
理解: 线程池就相当于一个公司, 核心线程数就相当于正式工人数, 新公司成立, 接一个新任务就会招聘一个正式工, 即使有空闲的正式工, 来了新任务也会招聘新的正式工来完成新任务, 直到招满为止. 招满后, 新任务就由空闲的正式工来完成
2. workQueue 阻塞队列
当线程池中的线程数目达到指定的corePoolSize后,并且所有的核心线程都在使用中, 再来获取线程将会被添加到缓存任务的阻塞队列中,也就是workQueue
队列可以设置queueCapacity 参数,表示任务队列最多能存储多少个线程对象
理解: 正式工已经招满, 此时所有的正式工都在忙着工作, 再来的新任务, 就只能排队等待
3. maximumPoolSize 最大线程数
被使用的线程数大于或等于核心线程数,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。
被使用的线程数等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常
理解: 正式工已经招满, 此时所有的正式工都在忙着工作, 且队列中的新任务已达到队列上线, 需要招聘临时工, 来完成任务. 正式工和临时工的总数为公司的最大员工数, 如果所有的临时工也都有任务, 再来新任务公司予以拒绝
4. keepAliveTime 线程最大空闲时间
线程池中没有被使用的线程, 就会处于空闲(alive)状态, 只要超过keepAliveTime, 空闲的线程就会被销毁,直到线程池中的线程数等于corePoolSize
如果设置了allowCoreThreadTimeOut=true(默认false),核心线程也可以被销毁
理解: 到了淡季, 公司不忙了, 很多的正式工和临时工都空闲了, 临时工就直接解雇了, 而正式工没有重大违纪的不会解雇
5. unit keepAliveTime 时间单位
TimeUnit是枚举类型
包含如下时间单位
注:
1秒 = 1000毫秒 1毫秒 = 1000微秒 1微妙 = 1000纳秒6. threadFactory 线程工厂
主要用来创建线程
7. handler 线程池拒绝策略(面试题:线程池拒绝策略有哪些)
只有核心线程都在使用中,任务队列已满,且线程数量已经达到maximunPoolSize才会触发拒绝策略。或在调用shutdown()和真正关闭线程池之间提交的任务都会被决绝。因为线程池被shutdown()时,会等待线程池内线程对象执行结束,才关闭线程池
DiscardoldestPolicy: 从队列中去掉一个最先进入队列的任务。然后重新提交被拒绝的任务(重复此过程)
AbortPolicy:丢弃任务,抛出运行时异常(RejectedExecutionException)(默认值)
CallerRunsPolicy:由调用该任务的线程处理, 线程池不参与
DiscardPolicy:丢弃任务,不抛出异常
8. 线程池总结 (常见面试题)
corePoolSize: 核心线程数大小
maximunPoolSize:最大线程数大小(包含核心线程数,剩下的就是普通线程数)
queueCapacity:任务队列最大长度
keepAliveSize:线程最大空闲时间
allowCoreThreadTimeOut:核心线程超时是否被销毁(默认 false)
handler:拒绝策略
workQueue:任务队列类型
1. 当线程数小于核心线程数时,创建线程, 直到到达核心指定的核心线程数
2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列
3. 当线程数大于等于核心线程数,且任务队列已满
i. 若线程数小于最大线程数,创建线程, 直到达到最大线程数
ii. 若线程数等于最大线程数,抛出异常,拒绝任务public class ThreadPoolExecutorTest {
/**
* 直接使用有参构造
*/
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor tp =
new ThreadPoolExecutor(2,2,10, TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(1));
//使用线程池
/**
* 子线程运行完之后,自动归还到线程池
*/
tp.execute(new Runnable() {
@Override
public void run() {
System.out.println("第一个线程");
System.out.println(Thread.currentThread().getName());
for (;;){}
}
});
tp.execute(new Runnable() {
@Override
public void run() {
System.out.println("第二个线程");
System.out.println(Thread.currentThread().getName());
for (;;){}
}
});
tp.execute(new Runnable() { //此时线程池阻塞
@Override
public void run() {
System.out.println("第三个线程");
System.out.println(Thread.currentThread().getName());
}
});
tp.shutdown();
}
}
3.4 Executors
Executors可以帮助快速实例化特定类型的线程池对象, Executors属于一个工具类
返回值都是ExecutorService接口的实现类, 底层都是调用ThreadPoolExecutor()
实例代码: ```java /**Executors创建线程池 / public class ThreadPool03 { public static void main(String[] args) { /*
* 核心线程数5
* 最大线程数5
* 线程不会销毁
*/
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName());
}); /**
* 同步队列 */
ExecutorService executorService1 = Executors.newCachedThreadPool(); /**
* 预定线程池中的线程执行 */
ScheduledExecutorService sec = Executors.newScheduledThreadPool(5); /**
* 直接执行 */
sec.submit(()->{
System.out.println(Thread.currentThread().getName());
}); /**
* 到达指定的时间,执行该线程 */
sec.schedule(()->{
System.out.println(Thread.currentThread().getName());
},10, TimeUnit.SECONDS);
}
}
<a name="6oOQF"></a>
#### 3.4.1 newCachedThreadPool()
构造方法参数:<br />corePoolSize: 0<br />maximumPoolSize: Integer的最大值<br />keepAliveTime: 60秒<br />workQueue:SynchronousQueue<br />同步队列, 一个线程向队列put(放入)数据后,阻塞等待被take(取出)<br />效果总结:线程需要时就新建,空闲60秒后被销毁。默认都是放在同步队列中。<br />![](https://cdn.nlark.com/yuque/0/2021/png/22310212/1629296939083-e4ca5fa8-a623-4d99-bb24-a447796abf61.png#height=85&id=hyU5K&originHeight=145&originWidth=712&originalType=binary&ratio=1&status=done&style=none&width=415)
<a name="eO5SE"></a>
#### 3.4.2 newFixedThreadPool(int)
构造方法参数:<br />corePoolSize: 参数值<br />maximunPoolSize: 参数值<br />keepAliveTime: 0秒<br />workQueue: LinkedBlockingQueue<br />生产者端和消费者端分别采用了独立的锁来控制数据同步, 这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能<br />效果总结:<br />最大线程数和核心线程数一致。 即:从0开始新建,当达到固定值后不新建,也不销毁线程对象(销毁数量:最大线程数 - 核心线程数)<br /> <br />![](https://cdn.nlark.com/yuque/0/2021/png/22310212/1629296939340-48ad7111-a656-4363-867c-1dffbbd1cf1c.png#height=72&id=zKnRE&originHeight=132&originWidth=758&originalType=binary&ratio=1&status=done&style=none&width=415)
<a name="1pp2y"></a>
#### 3.4.3 newScheduledThreadPool(int)
构造方法参数:<br />corePoolSize: 参数值<br />maximunPoolSize: int的最大值<br />keepAliveTime: 0秒<br />workQueue: DelayedWorkQueue<br />DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中放入数据的操作永远不会被阻塞,而只有获取数据的操作才会被阻塞<br />效果总结:<br />指定核心线程数,超出核心线程数的任务放入到延迟队列中,线程空闲立即被回收
<a name="ppadK"></a>
#### 3.4.4 newWorkStealingPool()
该方法是Java 8 新增的一个方法。底层使用的是ForkJoinPool类<br />![](https://cdn.nlark.com/yuque/0/2021/png/22310212/1629296939858-c042b33f-229f-4fbb-8f27-67fa932ac611.png#height=111&id=K5bVk&originHeight=156&originWidth=582&originalType=binary&ratio=1&status=done&style=none&width=415)
<a name="aUaKE"></a>
#### 3.4.5 ForkJoinPool介绍
ForkJoinPool是Java7新增的线程池类型。是ThreadPoolExecutor的兄弟类<br />工作原理(工作窃取算法):把一个Thread 分叉(fork)成多个子线程。让多个子线程执行本来一个线程应该执行的任务。最后把多个线程执行结果合并<br /> ![](https://cdn.nlark.com/yuque/0/2021/png/22310212/1629296940080-440c8655-09aa-4cad-84f9-8826e98adaa4.png#height=185&id=iWksV&originHeight=468&originWidth=664&originalType=binary&ratio=1&status=done&style=none&width=262)<br />如果在分叉后一个线程执行完成,另外的线程还没有结束,会重双端队列中尾部处理任务,另一个线程从头部取任务,防止出现线程竞争<br /> ![](https://cdn.nlark.com/yuque/0/2021/png/22310212/1629296940494-e0494490-8e74-48ea-b4ec-af85454a90ea.png#height=174&id=OmyUD&originHeight=358&originWidth=443&originalType=binary&ratio=1&status=done&style=none&width=215)<br />所有任务都是**异步**的。不会阻塞主线程,主线程不用等到线程池任务结束在关闭
<a name="3LWYq"></a>
#### 3.4.6 线程池代码演示
创建了固定长度为10的线程池<br />每个线程任务是输出随机数后休眠500毫秒<br />每次10个核心线程执行任务
```java
public class Threadpool {
public static void main(String[] args) {
//创建线程池, 核心线程数和最大线程数都为10, 一次最多有10个线程工作
ExecutorService executorService = Executors.newFixedThreadPool(10);
//分配100个任务
for (int i = 0; i < 50; i++) {
//将任务交给线程池, 每次10个核心线程并行执行
executorService.submit(new Runnable() {
@Override
public void run() {
try {
int i1 = new Random().nextInt(11);
System.out.println(Thread.currentThread().getName()+": "+i1);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//关闭线程池, 即使执行了关闭线程池, 也会等待线程池中的任务执行完才会关闭
executorService.shutdown();
}
}
四. JUC中的AtomicInteger原子类
4.1 原子性介绍
原子性:数据操作是整体,整体只有成功或失败,不允许出现部分成功部分失败
例如:
num++在多线程下执行是不安全的, num++可以分解为
1. 读取num的值(从主内存读取)
2. 对num+1(副本内存操作)
3. 把结果赋值给num(更新主内存)
所以说num++是不具备原子性的
如果希望num++具备原子性,可以把num++的三个步骤看做一个不可分的整体。只要具备了原子性,就一定是线程安全的
4.2 JUC中原子类
4.3 使用AtomicInteger保证线程安全
4.3.1原始方式: 使用synchronized同步锁解决
public class Demo07 {
static int a = 0;
public static void main(String[] args) {
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
//创建五个任务
for (int i = 0; i < 5; i++) {
//五个核心线程处理任务
executorService.submit(new Runnable() {
@Override
public void run() {
//保证数据的安全性, 使用同步锁
synchronized ("锁"){
for (int i1 = 0; i1 < 10000; i1++) {
a++;
}
}
}
});
}
try {
Thread.sleep(2000);
System.out.println(a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.3.2原子类方式: 使用AtomicInteger原子类
public class Demo08 {
public static void main(String[] args) {
//使用int原子类, 初始值设置为0
AtomicInteger atomicInteger = new AtomicInteger(0);
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
//创建五个任务
for (int i = 0; i < 5; i++) {
//五个核心线程处理任务
executorService.submit(new Runnable() {
@Override
public void run() {
for (int i1 = 0; i1 < 10000; i1++) {
//先+1再获取
int i2 = atomicInteger.incrementAndGet();
System.out.println(Thread.currentThread().getName()+": "+i2);
}
}
});
}
try {
Thread.sleep(2000);
System.out.println("总和:"+atomicInteger.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.3.3 原子类AtomicInteger方法介绍
public class Demo09 {
public static void main(String[] args) {
//创建原子类AtomicInterger, 设置初始值为1
AtomicInteger atomicInteger = new AtomicInteger(1);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 1; i++) {
//五个核心线程处理任务
executorService.submit(new Runnable() {
@Override
public void run() {
for (int i1 = 0; i1 < 1; i1++) {
System.out.println("------");
//先+1, 再获取
int i2 = atomicInteger.incrementAndGet();
System.out.println(i2);
//先获取, 再+1
int andIncrement = atomicInteger.getAndIncrement();
System.out.println(andIncrement);
//先-1, 再获取
int i3 = atomicInteger.decrementAndGet();
System.out.println(i3);
//先获取, 再-1
int andDecrement = atomicInteger.getAndDecrement();
System.out.println(andDecrement);
//先+指定的值, 再获取
int i4 = atomicInteger.addAndGet(100);
System.out.println(i4);
//先获取, 再+指定的值
int andAdd = atomicInteger.getAndAdd(200);
System.out.println(andAdd);
//获取值
int i5 = atomicInteger.get();
System.out.println(i5);
System.out.println("======");
}
}
});
}
}
}
4.3.4原子类AtomicInteger底层实现
原子类AtomicInteger底层使用的是volatile和cas
4.4 Volatitle(面试题)
在多线程环境下,volatile 关键字可以保证共享数据的可见性,有序性, 但是并不能保证对数据操作的原子性(数据操作是整体,整体只有成功或失败,不允许出现部分成功部分失败)。也就是说,多线程环境下,使用 volatile 修饰的变量是线程不安全的
4.4.1 可见性
内存可见性是指当一个线程在工作内存中修改了某个变量的值,其它线程总是能立即知道这个变量的变化。也就是说,如果线程 A 修改了共享变量 V 的值,那么线程再B 在使用 V 的值时,能立即读到 V 的最新值(排除B正在用使用变量V)
4.4.2 可见性原理
现在CPU很少有单核CPU,都是双核、双核四线程、四核、四核八线程甚至更好的CPU。 CPU 的速度是极高的,如果 CPU 需要存取数据时都直接与主内存打交道,一定没有直接操作自己的高速缓存效率高,所以,为了提高处理速度,CPU 不直接和主内存进行通信,而是在 CPU 与内存之间加入高速缓存,它们比直接操作主内存的速度高的多
由于 CPU 与内存之间加入了缓存,在进行数据操作时,先将数据从内存拷贝到缓存中,CPU 直接操作的是缓存中的数据。但在多处理器下,将可能导致各自的缓存数据不一致(这也是可见性问题的由来),为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,而总线嗅探机制是实现缓存一致性的常见机制
嗅探机制工作原理
每个处理器通过监听在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址修改(必须在修改该变量之前),就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从主内存中把数据读到处理器缓存中
4.4.3 有序性
什么是指令重排?
为了提高性能,编译器和处理器常常会对指令做重排序。
一般重排序可以分为如下三种类型:
1. 编译器优化重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
2. 指令级并行重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3. 内存系统重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
数据依赖性:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑
从 Java 源代码到最终执行的指令序列,会分别经历下面三种重排序:
4.4.4 有序性原理
使用 volatile 修饰变量时,Java 编译器在生成字节码时,会在指令序列中插入内存屏障(CPU处理器的指令)来禁止CPU处理器重排序
4.5 CAS算法(面试题)
4.5.1 CAS算法介绍
CAS( Compare And Swap)算法,比较和交换算法。
CAS不需要和synchronized一样让对象具有独立性、互斥性保持线程安全。而是一种无锁也可以保证线程安全的算法。
理解:
给定代码:
//num++
num=num+1;
多个线程同时执行这段代码
synchronized: 一次只能有一个线程操作
CAS算法: 实现思路
线程开启时候,每个线程在主内存中拷贝一个变量副本, 每个线程操作自己的副本
1. 首先获取到num在主内存中值, 存储为一个副本
2. 然后对num的值+1
3. 修改主内存中num值时,会比较主内存中的num值是否和副本中值相同
a. 如果主内存中num值和副本值相同,把主内存num值更新为新的值
b. 如果内存中num值和副本值不同,会从第1步重新开始执行
4.5.2 优点
保证数据操作的原子性,保证了线程是安全的
这个算法相对synchronized是比较“乐观的”,它不会像synchronized一样,当一个线程访问共享数据的时候,别的线程都在阻塞。synchronized不管是否有线程冲突都会进行加锁。由于CAS是非阻塞的,它死锁问题天生免疫,并且线程间的相互影响也非常小,更重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,所以它要比锁的方式拥有更优越的性能
悲观锁:真正的加锁,比如synchronized
乐观锁:通过其他的方式实现锁的效果,比如CAS算法
4.5.3 缺点
多线程操作时每次修改值时,可能多次出现内存值和副本值不同的情况,需要循环执行多次
可能出现ABA问题
4.5.4 ABA问题
所谓ABA问题,其实用最通俗易懂的话语来总结就是狸猫换太子
比较并交换的循环,存在一个时间差,而这个时间差可能带来意想不到的问题
比如有两个线程A、B:
1. 主内存中变量为 V=3
1. 两个线程A, B, 都拷贝了主内存中的变量 E=3
2. A线程执行到N=V+0, 即N=3 此时A线程挂起
3. B线程修改原值为N=V+1,即N=4,
4. 然后B觉得修改错了,然后再重新把值修改为3;
5. A线程被唤醒,执行this.compareAndSwapInt()方法,发现这个时候主内存V的值等于线程A中E的值, 都为3,(但是却不知道B曾经修改过), A线程修改成功。
6. 尽管线程A的CAS操作成功,结果也没有问题, 但不代表就没有问题, V的值是发生过改变的
a) 有的需求,只注重结果,结果一直就可以, 可以不用考虑ABA的问题
b) 但是有的需求,需要注重过程,必须考虑ABA的问题
4.5.5 解决ABA问题
为了解决ABA问题,一般都会在操作时设置一个int类型版本号(version),每次对内存中变量操作后都让版本号加1。当需要修改变量时,除了比较副本中值和内存值以外,还需要比较版本号是否相同。JDK中AtomicStampedReference就是这种方式,提供了全局int属性
4.6 源码分析AtomicInteger原子类
4.6.1 底层实现
原子类AtomicInteger底层使用的是volatile和cas
volatitle保证了可见性, 有序性, cas保证了原子性
CAS算法的过程是这样:它包含3个参数CAS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。
当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即时没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理
4.6.1 源码重点
jdk8
jdk11
public class AtomicInteger extends Number implements java.io.Serializable {
/*
* 序列化id
* 序列化时和反序列化时都使用该Id
* */
private static final long serialVersionUID = 6214790243416807050L;
/*
* 设置为使用Unsafe.compareAndSwapInt进行主内存的更新
* 属于底层的操作, 直接操作主内存. 不建议手动调用
* */
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
/*
* value属性的属性偏移量(理解: value属性的引用地址)
* 参数一: AtomicInteger类对象
* 参数二: 属性名
* 根据类对象和属性名就可以获取该属性的属性对象
* */
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
/*
* value由volatile修饰, 可以保证可见性和有序性
* */
private volatile int value;
/*有参构造方法
* 使用给定的初始值创建AtomicInteger
* 即value = initialValue;
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/*无参构造方法
* 创建一个初始值为0的AtomicInteger实例
* 即value=0;
*/
public AtomicInteger() {
}
/*
* 获取当前value的属性值
*/
public final int get() {
return value;
}
/*
* 设置当前value的属性值
*/
public final void set(int newValue) {
value = newValue;
}
/*
* 原子级设置为给定值并返回旧值
*/
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
/*
* 以原子方式将值设置为给定的更新值
* @param expect 预期值
* @param update 新值
* @return 成功返回true 返回false表明实际值不等于预期值,设置失败
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/*
* 以原子方式将值设置为给定的更新值
* <p><a href="package-summary.html#weakCompareAndSet"可能会失败
* @param expect 期望值
* @param update 新值
* @return 成功返回true 返回false表明实际值不等于预期值,设置失败
*/
@Deprecated(since="9")
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/*
* 原子上增加一个当前值。
* @return 之前的值
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
getAndAddInt函数的方法体,传进来的var4是1,每调用一次增加1.compareAndSwapInt
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
*/
/*
* 原子地将当前值减1。
* @return 之前的值
*/
//和上面一样,传进去的var4是-1,所以每次调用减1
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
/*
* 原子上将给定值添加到当前值。
* @param delta 添加值
* @return 旧值
*/
//和上面一样,但是这里传进去的var4是delta而已
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
/*
* 原子上当前值加1。
* @return 新值
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//前面说getAndAddInt返回的是之前的值,调用这个函数把这个值+1,返回+1前的值之后+1,就等于现在的值
/*
* 原子地将当前值减1。
* @return 新值
*/
//还是一样。换成-1.变成减而已
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
/*
* 原子上将给定值添加到当前值。
* @param delta 添加值
* @return 新值
*/
//还是一样。换成delta值
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
}
4.7 API整理
4.7.1 常见方法
incrementAndGet(): 先加1,在获取。类似于++num
getAndIncrement():先获取,后加1。类似于num++
addAndGet(int);先在原值上增加指定值,后获取结果
getAndSet(int):先获取,后设置值
decrementAndGet():先减1,在获取。类似—num
getAndDecrement():先获取,在减1.类似num—
set(int):设置值
get()返回value值
set()设置value值