一、创建线程的3种方式
- 学习视频地址:https://www.bilibili.com/video/BV1Z54y1j7JT?p=3&share_source=copy_web
- 1.继承 Thread 类
- 2.实现Runnable接口
- 3.实现 Callable 接口
- Thread 类的静态方法: currentThread( )
- 底层 toString 打印内容
- 获取名称—— getName
- 设置名称——setName
三、操作线程的基本方法 1. 使当前正在执行的线程进入休眠状态线程优先级范围:1~10
- 设置: setPriority
- 获取:getPriority
- Thread 的静态方法: sleep(long millis 毫秒数)
-
位置
-
类型
-
作用
-
线程数量
-
调用次数:方法调用多次,是否会出现新的问题
- Thread 类的 interrupt 方法
- 1.用于停止正在运行的线程
- 2.如果停止休眠中的线程,则会抛出线程中断异常
使用
问题:线程没有停下来,程序也并未结束
- 该方法只是将线程标记为中断状态,实际并未中断
- 需要在 run 方法中通过 isInterrupted 方法主动去判断线程是否被中断(是否有中断标记)
Thread.yield( ) 方法
- 用于一些需要一定条件才执行的任务
- 未满足条件时,调度到了该线程就放弃执行权
1、编写打印任务和赋值任务
-
打印线程
-
赋值线程
2、 主线程
3、比较代码段的不同
- 空循环较为浪费资源
- yield 可以主动放弃执行权,而非被动地被调度
Thread.join( ) :等待调用该方法的线程死亡
用于使多个线程按顺序执行
例子
- 线程1
- 线程2
- 线程3
- 执行
使 A 线程随着 B 线程的结束而结束——设置 A 线程 为 B 线程的守护进程
1、设置后台线程
测试
2、判断线程是否为后台线程
3、主线程
- 由执行结果可知,守护线程确实随着主线程的结束而结束了,并未受死循环影响。
通过判断线程是否存活
8. 线程组测试
1、获取线程组
- thread.getThreadGroup( )
2、创建线程组
3、设置线程组
4、线程组常用方法
四、线程同步:synchronized 1. synchronized 关键字- 可用来给对象和方法或者代码块加锁
- 被synchronized 修饰的代码,,同一时刻最多只有一个线程执行这个方法/这段代码。
出现问题:有重复票
修改建议:
- 需要添加同步操作
- 但不要将整个 while 循环放入同步代码块中,否则会出现一个线程把票买完的情况
问题:出现错票
测试3:同步代码块_有双检问题原因:只剩 1 个资源,但有多个线程同时判断到有资源可用
记住问题修改:进入同步代码块后,再进行一次条件检测
- 重复票:无同步操作
- 错票:有同步操作,但没有同步操作内外双重判断
- 同步锁是为了保证每个线程都能正常执行原子不可更改操作,【同步监听对象/同步锁/同步监听器/互斥锁】的一个标记锁。
- 同一时刻,最多只有一个线程拿到同步锁,然后执行同步代码。
- 对象类型
- 类类型
结果说明:当线程 sleep 时,是不会释放锁的
修改测试:Task.class → Main.class
总结
- 线程的 sleep 方法,不会释放锁
- 争夺同一把锁,线程会阻塞
- 死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
- 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。
- 两个或两个以上的线程争夺彼此的锁,造成阻塞,程序永远处于阻塞状态。
- 资源互斥:一个资源只能同时被一个线程占有
- 请求与保存:线程持有其资源的情况下,能申请获取另一个资源
- 不剥夺:线程任务未完成时,不主动释放自己的资源
- 循环等待:两个以上线程,构成环
-
实现思路
- 两个或两个以上的线程
- 两个或两个以上的锁(资源)
- 两个或两个以上的线程去持有不同的锁
- 持有着不同锁的线程(即在锁住的代码内容中)去争夺对方的锁
-
然后程序在输出 A、B之后,进入死锁。
- 首先需要有一个锁对象:lock
- 然后某个线程用 synchronized ( lock ) 获得该锁后,可调用 lock.wait( ) 方法,此时发生
- 让出这个锁资源:lock
- 同时线程开始无限期等待(如果没设置等待时间)
- 等待 lock.notify() (如果只有1条与该锁相关的线程,则被唤醒)或 lock.notifyAll 被唤醒继续执行
- 如果有多个线程执行 task ,则 notify 只唤醒1个线程
- 位置
- 是否需要当前线程拥有锁
- 是否支持手动唤醒?
- 是否支持自动唤醒?
- 是否支持中断?
- 是否释放锁?
- 线程状态
位置
- sleep:是 Thread 类的静态方法
- wait:是 Object 类的方法
总结
3. 线程间通讯(wait & notify 应用) 1、数据类thread.getState( ) :获取线程状态
public class Data {
private String message;
public void setMessage(String message){
// 设置数据信息
this.message = message;
}
public String getMessage() {
return message;
}
}
2、生产者类
package lab;
public class Producer implements Runnable {
private Data data;
private int count = 0;
public Producer(Data data) {
// 设置数据
this.data = data;
}
@Override
public void run() {
// 无限循环
while (true) {
try {
// 让当前线程睡1秒钟
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 同步对象为data的同步代码块
synchronized (data) {
// 当数据信息不为空时,即还有数据信息未消费
while (data.getMessage() != null) {
try {
// 使当前生产者线程等待
data.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 数据信息
String message = "你好!" + count++;
// 设置数据信息
data.setMessage(message);
// 唤醒正在此对象监视器上等待的所有线程
data.notifyAll();
}
}
}
}
3、消费者类
package lab;
public class Consumer implements Runnable {
private Data data;
public Consumer(Data data) {
// 设置数据
this.data = data;
}
@Override
public void run() {
// 无限循环
while (true){
// 同步对象为data的同步代码块
synchronized (data) {
// 当数据信息为空时
while (data.getMessage() == null) {
try {
// 等待
data.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费数据信息
System.out.println("数据信息:" + data.getMessage());
// 将数据对象里面的数据信息设置为null
data.setMessage(null);
// 唤醒正在此对象监视器上等待的所有线程
data.notifyAll();
}
}
}
}
4、主线程测试
package main;
import lab.Consumer;
import lab.Data;
import lab.Producer;
public class Main {
public static void main(String[] args) {
// 数据
Data data = new Data();
// 创建生产者任务和消费者任务
Producer producer = new Producer(data);
Consumer consumer = new Consumer(data);
// 创建生产者线程和消费者线程
Thread producerThread1 = new Thread(producer);
Thread consumerThread1 = new Thread(consumer);
// 启动线程
producerThread1.start();
consumerThread1.start();
}
}
六、线程同步:Lock 锁
具备和synchronized一样的作用,可以实现线程同步,但比synchronized 更强大。
Lock接口定义了 6 个方法
常用实现类
使用
-
package main; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { public static void main(String[] args) { // 创建显式锁对象 Lock lock = new ReentrantLock(); // 通过匿名内部类方式实现Runnable接口 Runnable runnable = new Runnable() { @Override public void run() { // 获取锁 lock.lock(); try { // 使当前线程睡1秒钟 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 输出语句 System.out.println(Thread.currentThread().getName() + " --- run"); } }; // 创建线程 Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); Thread thread3 = new Thread(runnable); // 启动线程 thread1.start(); thread2.start(); thread3.start(); } }
总结
-
发现锁未释放,则一直等待
- 发现锁未释放,则先去做其他事
使用格式
测试
package main;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Task implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
// 非阻塞式获取锁
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 使当前线程休眠1秒钟
Thread.sleep(1000);
// 输出当前线程名称
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
} else {
System.out.println("锁被占用,执行其他任务。");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package main;
public class Main {
public static void main(String[] args) {
// 创建任务
Task task = new Task();
// 创建线程
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
// 启动线程
thread1.start();
thread2.start();
}
}
3、尝试定时获取锁
2. 中断等待锁的线程
可中断测试
拿到锁持有 3 秒钟
thread0持有锁时,还未释放,主线程此时主动中断 thread1
发生异常
- 注意:该异常是 thread1 获取到锁之后抛出的
- 即 3 s 后,线程 thread1 已经拿到了锁,但发现自己已经被中断了,所以抛出异常
- 当一个线程获取了锁之后,是不会被interrupt()方法中断的。
- 因为调用 interrupt( ) 方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程,而进入阻塞状态前是需要判断获取锁的
- 因此当通过 lockInterruptibly( ) 方法获取某个锁时,如果不能获取到,则可通过抛出异常来响应中断。
- 对比 lock 方法,该方法操作性更强
-
通过 Condition 实现的
-
每次返回的都是新的 Condition 实例
方法对应
常用方法
package main;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args) {
// 创建显式锁
Lock lock = new ReentrantLock();
// 获取Condition对象
Condition condition = lock.newCondition();
// 创建线程
Thread thread = new Thread(){
@Override
public void run() {
try {
// 获取锁
lock.lock();
// 输出语句
System.out.println(Thread.currentThread().getName() + " --- 等待");
// await()方法造成当前线程在等待,直到它被唤醒,通常由被通知或中断。
condition.await();
// 输出语句
System.out.println(Thread.currentThread().getName() + " --- 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
};
// 启动线程
thread.start();
}
}
测试唤醒
- 主线程添加代码
- 注:需要主线程先获取到 Lock 显式锁
测试唤醒全部
- 主线程添加代码
1、数据类
-
定义两个不同的 Condition
-
package lab; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Data { private String message; private int count = 0; private boolean empty = true; private Lock lock = new ReentrantLock(true); private Condition setCondition = lock.newCondition(); private Condition getCondition = lock.newCondition(); public void setMessage(String message) throws InterruptedException { // 获取锁 lock.lock(); try { // 当数据信息不为空时,即还有数据信息未消费 while (!empty) { // 使当前生产者线程等待 setCondition.await(); } // 设置数据信息 this.message = message + Thread.currentThread().getName() + " --- " + count++; // 数据信息没有被消费 empty = false; try { // 让当前线程睡1秒钟 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 唤醒正在此对象监视器上等待的单个线程 getCondition.signal(); } finally { // 释放锁 lock.unlock(); } } public String getMessage() throws InterruptedException { // 获取锁 lock.lock(); try { // 当数据信息为空时 while (empty) { // 使当前生产者线程等待 getCondition.await(); } // 数据信息已经被消费 empty = true; // 唤醒正在此对象监视器上等待的单个线程 setCondition.signal(); } finally { // 释放锁 lock.unlock(); } // 返回数据信息 return message; } }
2、生产者
package lab;
public class Producer implements Runnable {
private Data data;
public Producer(Data data) {
// 设置数据
this.data = data;
}
@Override
public void run() {
try {
// 无限循环
while (true) {
// 设置数据信息
data.setMessage("你好!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3、消费者
package lab;
public class Consumer implements Runnable {
private Data data;
public Consumer(Data data) {
// 设置数据
this.data = data;
}
@Override
public void run() {
try {
// 无限循环
while (true){
// 消费数据信息
System.out.println("数据信息:" + data.getMessage());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试
package main;
import lab.Consumer;
import lab.Data;
import lab.Producer;
public class Main {
public static void main(String[] args) {
// 数据
Data data = new Data();
// 创建生产者任务和消费者任务
Producer producer = new Producer(data);
Consumer consumer = new Consumer(data);
// 创建生产者线程和消费者线程
Thread producerThread1 = new Thread(producer);
Thread consumerThread1 = new Thread(consumer);
// 启动线程
producerThread1.start();
consumerThread1.start();
}
}
4. 可重入锁和不可重入锁利用await、signal、signalAll等待唤醒机制,让线程间友好协作完成任务,更高效地使用资源。
- (不)可:(不)可以
- 重入:重复进入同步作用域(使用同步锁)
- 锁:同步锁
测试 synchronized 可重入锁
说明可重复使用持有的锁
同步方法上的同步锁,也可重复使用(毕竟锁住了整个类)
测试 lock 可重入锁
测试不可重入锁
自定义不可重入锁的思路
- 实现 Lock 接口——着重实现 lock 和 unlock 方法即可
- 绑定已获取锁的线程
- 实现获取锁的方法
- 实现释放锁的方法
package lab;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class NotReentrantLock implements Lock {
private Thread thread;
@Override
public void lock() {
// 获取当前线程
Thread currentThread = Thread.currentThread();
// 当当前线程为已经拿到锁的线程时
while (currentThread == thread) {
// 同步对象为this的同步代码块
synchronized (this) {
try {
// 使当前线程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 记录当前线程
thread = currentThread;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
// 获取当前线程
Thread currentThread = Thread.currentThread();
// 当当前线程不为绑定线程时
while (currentThread != thread) {
// 同步对象为this的同步代码块
synchronized (this) {
try {
// 使当前线程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 将绑定线程置空
thread = null;
// 同步对象为this的同步代码块
synchronized (this){
// 唤醒等待该锁的所有线程
notifyAll();
}
}
@Override
public Condition newCondition() {
return null;
}
}
5. 公平锁与非公平锁
线程获取锁的机会平等与否
- 非公平锁(synchronized)
- 非公平锁(ReentrantLock)——默认是非公平的 ( false )
- 公平锁(ReentrantLock)
- 判断锁是否公平(isFair)——只能判断 ReentrantLock 和 ReentranReadWriteLock
- reentrantLock.isFair( )
- reentranReadWriteLock.isFair()
线程启动后,几乎可以同时拿到读锁
2、写锁- 三条线程依次输出(独占的,一个用完再给下一个)
- 能保证支持多个线程同时读取的时候,禁止修改
- 能保证同一时刻只有一个线程在修改
1、高并发容器
-
键值对容器
-
锁
- 读锁——获取数据
- 写锁——存储数据
-
方法
- 获取数据
- 存储数据
- 获取所有键
- 清空容器
-
package lab; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ConcurrentDictionary { private Mapm = new TreeMap<>(); private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true); private ReentrantReadWriteLock.ReadLock r = rwl.readLock(); private ReentrantReadWriteLock.WriteLock w = rwl.writeLock(); public String put(String key, String value) { // 获取写锁 w.lock(); try { // 添加数据并将旧值返回给调用者 return m.put(key, value); } finally { // 释放写锁 w.unlock(); } } public String get(String key) { // 获取读锁 r.lock(); try { // 返回键所对应的值 return m.get(key); } finally { // 释放读锁 r.unlock(); } } public List allKeys() { // 获取读锁 r.lock(); try { // 返回容器中所有的key值 return new ArrayList<>(m.keySet()); } finally { // 释放读锁 r.unlock(); } } public void clear() { // 获取写锁 w.lock(); try { // 清空容器 m.clear(); } finally { // 释放写锁 w.unlock(); } } }
2、写任务
3、读任务
4、测试
8. synchronized 和 Lock 的区别 1、类型- synchronized 关键字
- Lock 接口
- 多个锁嵌套之后,释放的方式
-
synchronized 只有非公平锁
-
ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true); //写锁 Lock writeLock = readWriteLock.writeLock(); //读锁 Lock readLock = readWriteLock.readLock();
LockSupport
-
功能
- 使线程等待
- 唤醒当前等待中的线程
-
该工具类无法被实例化
- 按功能分类
- 都是静态方法,可直接通过类名调用
思路:
- 锁标志——记录锁是否已经被其他线程使用
- 等待队列
- 处理线程中断——有线程不想等了,需要处理
实现:
- 先进先出互斥锁:FIFOMutex
- 上锁方法的实现:lock( )
- 互斥锁的实质:同一时刻只有1个线程能用该锁,所以需要一个等待队列辅助排队。
- Task类
- Main
-
普通的读写锁,存在“线程饥饿”问题
-
会导致写线程饥饿的情况:
- 当线程 A 持有读锁读取数据时,线程 B 要获取写锁修改数据就只能到队列里排队。
- 此时又来了线程 C 读取数据,那么线程 C 就可以获取到读锁,而要执行写操作线程 B 就要等线程 C 释放读锁。
- 由于该场景下读操作的数量远远大于写操作的数量,此时可能会有很多线程来读取数据而获取到读锁,那么要获取写锁的线程 B 就只能一直等待下去,最终导致饥饿。
-
StampedLock是基于CLH锁原理实现的
- CLH是一种基于排队思想实现的自旋锁,可以保证FIFO(先进先出)的服务顺序,所以会避免写线程饥饿问题
- 其实就是其中实现了一个队列,每次不管是读锁也好写锁也好,未拿到锁就加入队列
- 然后每次解锁后,队列头存储的线程节点获取锁,从而避免饥饿。
-
StampedLock 常用方法
- 可重入性和普通的读写锁有区别
获取线程状态的方法
- 线程状态
- 线程周期
本质
- Thread内部有一个ThreadLocalMap的成员变量,里面存放着和这个线程绑定的数据。
- 虽然ThreadLocalMap叫map,但是并没有实现map接口,只是提供了几个和map接口一样的方法
- ThreadLocal作为ThreadLocalMap的key
- 所以说ThreadLocal本身并不存放数据,数据都在Thread对象中的ThreadLocalMap里
- 所以同一个 ThreadLocal,在不同线程Thread的ThreadLocalMap中作为 key ,其 value 可以不同,相对独立。
ThreadLocal的定位是什么?
-
Thread 类的 ThreadLocalMap 属性的 key 值
-
key就是 ThreadLocal
-
ThreadLocal与Thread 的关系是什么?
- ThreadLocal 与Thread是相互协作的关系。
- Thread负责存储数据,ThreadLocal负责操作数据。
简单测试
- 输出为 null
-
修改
-
删除—— remove 方法
InheritableThreadLocal
- 继承自 ThreadLocal,方法一样
- 就当做是属性值一样来用
- 并不是说,线程 B extends 自线程 A
- 而是,【线程 B】 需要在【线程 A】 的 run 方法中创建
- 然后即可共享线程 A 中的本地变量
测试
- 线程B
- 线程A
- Main
TransmitableThreadLocal
- 继承自 InheritableThreadLocal
- 该类非 JDK 自带的,而是 :https://github.com/alibaba/transmittable-thread-local
ThreadLocal 线程池测试
-
task
-
提交到线程池的任务,执行时无法获取到 ThreadLocal
-
InheritableThreadLocal 也一样
-
说明: ThreadLocal 和 InheritableThreadLocal 无法再线程池间共享
TransmitableThreadLocal 测试
发现仍然不一致;
需要按照规定使用:
-
方法一:改变线程池任务提交方式
- 借助 TtlRunnable 类,绑定任务 task
-
方法二:改变线程池
- 创建线程池:
- ExecutorService executorService = Executors.newFixedThreadPool( 1 );
- TtlExcutors.getTtlExecutorService( executorService );
- 正常提交任务即可
- 创建线程池:
十二、多线程基础扩展 1. 内存可见性——volatile总结
专门解决内存可见性问题的关键字
- 内存可见性
理解内存可见性
发现, thread 线程并未停下来。
原因:
volatile 解决该问题
修改代码:
实现内存可见性。
即使用 volatile 修饰的变量,会主动通知更新【对应线程的工作内存】
2. 原子性一个或多个操作不可中断的,要么全部执行成功,要么全部执行失败。
对应的操作——原子操作——具备原子性的操作
i++ 是原子操作吗?
- 不是
判断过程:让多个线程执行 i++ ,看最后的值是否为累加即可
发现,i++不是原子操作,其本质步骤分为:
每一步都有可能被其他线程干扰
- 1.从主内存读取变量i
- 2.执行i++
- 3.将变量i写入主内存
使用 volatile 无法解决原子性问题
添加 同步代码 可以解决
使用原子类解决
具有原子性的类
3. CAS (比较并交换)技术总结
比较并交换技术(CAS技术):CAS:Compare And Swap
CAS操作包含三个操作数:内存值、预期原值、新值
如果内存位置的值与预期原值相匹配,那么将内存值更新为新值;否则什么也不做
- 和谁比较
- 和谁交换
CAS操作包含三个值
- 内存中
- 预期原值
- 新值
测试线程干扰
- 线程干扰
- 当线程未执行到变量值修改的语句时,对应的变量值先被其他线程修改
CAS 方法抗干扰
CAS的应用
- 原子类里大多都是使用 CAS 方法来保证
4. 原子类总结
-
分类
CAS算法存在的ABA问题
- 假如一个值原来是A,现在变成了B,
- 后来又变成了A,
- 那么CAS检查时会发现它的值没有发生变化,
- 但是实际上却变化了
解决方法
- 打标记(version等)
- 原子类
修改
总结
- ABA问题:假如一个值原来是A,现在变成了B,后来又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了
- AtomicMarkableReference是一个带修改标记的引用类型原子类
- AtomicStampedReference是一个带版本号的引用类型原子类
3. CAS (比较并交换)技术总结
比较并交换技术(CAS技术):CAS:Compare And Swap
CAS操作包含三个操作数:内存值、预期原值、新值
如果内存位置的值与预期原值相匹配,那么将内存值更新为新值;否则什么也不做
- 和谁比较
- 和谁交换
CAS操作包含三个值
- 内存中
- 预期原值
- 新值
- [外链图片转存中…(img-LmiX1huB-1657340436259)]
测试线程干扰
- 线程干扰
- 当线程未执行到变量值修改的语句时,对应的变量值先被其他线程修改
CAS 方法抗干扰
[外链图片转存中…(img-cp5nN0uD-1657340436259)]
[外链图片转存中…(img-YTU9xp6Y-1657340436260)]
CAS的应用
- 原子类里大多都是使用 CAS 方法来保证
4. 原子类总结
-
分类
- [外链图片转存中…(img-7fJfXpFz-1657340436261)]
-
[外链图片转存中…(img-O03n1ML3-1657340436261)]
CAS算法存在的ABA问题
- 假如一个值原来是A,现在变成了B,
- 后来又变成了A,
- 那么CAS检查时会发现它的值没有发生变化,
- 但是实际上却变化了
解决方法
- 打标记(version等)
- 原子类
[外链图片转存中…(img-4aG1DCZM-1657340436262)]
[外链图片转存中…(img-VG9A8qWg-1657340436262)]
修改
[外链图片转存中…(img-JWrtHnOI-1657340436263)]
[外链图片转存中…(img-xRvNxT75-1657340436264)]
[外链图片转存中…(img-6DyzF1A5-1657340436264)]
总结
- ABA问题:假如一个值原来是A,现在变成了B,后来又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了
- AtomicMarkableReference是一个带修改标记的引用类型原子类
- AtomicStampedReference是一个带版本号的引用类型原子类



