- 为什么有多线程:
- 平衡cpu利用率和IO之间的差距。I0较慢,cpu需要等待,造成cpu利用率不高。那就增加多线程,进行分时复用,进而提升cpu利用率。
java线程和操作系统线程的关系
-
映射关系:
- 在现代Java虚拟机(如HotSpot JVM)中,Java线程通常直接映射到操作系统线程。这意味着每个Java线程在底层都是由一个操作系统线程来支持和管理的。
-
调度和管理:
- 由于Java线程是由操作系统线程支持的,因此它们的调度和管理主要依赖于操作系统的线程调度器。操作系统负责分配CPU时间给线程,并处理线程的切换。
- 线程状态,这个状态是在用jstack当前线程快照的状态:
-
1、新建:创建后尚未启动。
-
2、可运行:可能正在运行,也可能正在等待 CPU 时间片。包含了操作系统线程状态中的 Running 和 Ready。
-
3、阻塞:等待获取一个排它锁
-
4、无限期等待(Waiting):等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
-
5、限期等待(Timed Waiting):无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
-
6、死亡:可以是线程结束任务之后自己结束,或者产生了异常而结束。
-
Thread 下都有什么方法
-
Start,Sleep,Yield, Join, CurrentThread, Getid, getName(), Getstat , Isalive
-
#################################################################
-
join() 的作用:让主线程”等待“子线程”结束之后才能继续运行。即主线程等待子线程的终止,countdownlatch 比他灵活。
-
#################################################################
-
sleep 和 yield的区别:
-
线程执行 sleep() 方法后进入阻塞状态;线程执行 yield() 方法转入就绪状态,可能马上又得得到执行
-
sleep() 方法给其他线程运行机会时不考虑线程的优先级,yield() 方法只会给相同优先级或更高优先级的线程运行的机会
-
sleep() 方法声明抛出 InterruptedException;yield() 方法没有声明抛出异常
-
sleep 和 yield的 相同点:都只释放cpu,不释放锁。
-
##################################################################
-
sleep 和wait的区别:
-
Sleep可以用到任何的地方,而wait 必须用在同步代码块上。
-
Sleep()不释放同步锁, Wait()释放同步锁;
-
Sleep 需要指定阻塞时间,wait 需要notify来唤醒
结束线程的三种方式:
- 1.设置退出标志
public class MyThread extends Thread {
private volatile boolean running = true;
public void run() {
while (running) {
// 线程执行的任务
}
}
public void stopThread() {
running = false;
}
}
// 使用
MyThread thread = new MyThread();
thread.start();
2. 使用 interrupt() 方法
// 终止线程
thread.stopThread();
- 2.使用interrupt()方法中断线程
- 1、线程可以根据中断标志或捕获的
InterruptedException来决定是否终止。 2、如果线程处于阻塞状态(如sleep()、wait()或join()),调用interrupt()会抛出InterruptedException,并清除中断标志。
public class MyThread extends Thread {
public void run() {
while (!isInterrupted()) { // 检查中断标志
try {
System.out.println("Thread is running...");
Thread.sleep(1000); // 模拟线程工作
} catch (InterruptedException e) {
System.out.println("Thread was interrupted!");
// 捕获 InterruptedException 后,中断标志会被清除
// 如果需要终止线程,可以重新设置中断标志
interrupt(); // 重新设置中断标志
}
}
System.out.println("Thread is stopping...");
}
}
}
// 使用
MyThread thread = new MyThread();
thread.start();
// 中断线程
thread.interrupt();
- 3 使用 stop() 方法(不推荐)
Thread类提供了一个stop()方法,可以直接终止线程。然而,这种方法已被弃用,因为它可能导致线程在不一致的状态下终止,从而引发资源泄漏或数据损坏。
public class MyThread extends Thread {
public void run() {
// 线程执行的任务
}
}
// 使用
MyThread thread = new MyThread();
thread.start();
// 终止线程(不推荐)
thread.stop();
- 4.使用 ExecutorService 和 Future
- 如果你使用 ExecutorService 来管理线程,可以通过 Future.cancel(true) 来中断线程。本质也是通过标志位执行的isInterrupted;
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 线程执行的任务
}
});
// 终止线程
future.cancel(true);
executor.shutdown();
创建线程的方式
* 1、 实现Runnable接口,传入Thread 有参构成方法中。
* public interface Runnable {
* public abstract void run();
* }
* class Thread implements Runnable {
* public Thread(Runnable target) {
* init(null, target, "Thread-" + nextThreadNum(), 0);
* }
* }
* 2、 通过继承Thread类,重写Run 方法。
- ##################################################################
- Run方法和Start方法的区别
- Runnable只有一个run方法,它里面包含了具体要执行的业务代码,当调用 run 方法时,会立即执行 run 方法中的代码(如果当前线程时间片未用完)。执行 run 方法相当于执行普通方法,并不会开启新线程,可以被执行多次;
- 而调用 start 方法时,是开启一个线程并将线程的状态设置为就绪状态,并不会立即执行,所以start方法只能执行一次,也就是说线程只能被创建一次。
* 3、实现Callable接口,传入FutureTask。
- 在传入thread里方法中可以有返回值,并且抛出异常。(Callable和FutureTask通常配合起来使用,只是实现异步的异步的一种方式并不会创建线程)
- public static void main(String[] args) throws ExecutionException, InterruptedException { MyCallable mc = new MyCallable();
- FutureTask ft = new FutureTask<>(mc);
- Thread thread = new Thread(ft);
- thread.start();
- ft.get(); }
- Future获取得线程执行结果前,我们的主线程get()得到结果需要一直阻塞等待,即使我们使用isDone()方法轮询去查看线程执行状态,但是这样也非常浪费cpu资源。
- ##################################################################
- Callable 和 Runable 区别
- 方法名,Callable 规定的执行方法是 call(),而 Runnable 规定的执行方法是 run();
- 返回值,Callable 的任务执行后有返回值,而 Runnable 的任务执行后是没有返回值的;
- 抛出异常,call() 方法可抛出异常,而 run() 方法是不能抛出受检查异常的
- 联系: callable 本身也是也是通过适配器来实现run方法的,请看方法RunnableAdapter
* 4、使用线程池实。
- 使用线程池的好处:
- (1)、降低线程创建和销毁线程造成的开销
- (2)、提高了响应速度,任务到达时,相对于手动创建一个线程,直接从线程池拿速度更快
- (3)、提高线程可管理性。
- //创建线程池
- ThreadPoolExecutor pool=ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
- //执行线程
* threadPoolExecutor.execute(() -> { });// execute没有返回值,如果不需要知道线程的结果就使用exexute方法。方法中抛出一个未捕获的异常,可能会导致线程池的某个工作线程终止,然后会创建新的线程。
* Future future= threadPoolExecutor.submit(()-> 1+1);//Submit返回一个Future对象,可以通过Future get 方法获取线程的线程的异常,还能拿到通知结果。方法中抛出一个未捕获的异常线程不但不会被终止,异常会被捕获并封装在一个 Future 对象中。
线程内部的异常默认不会自动传递到主线程,除非显式捕获或通过特定机制(如 Future)获取。线程异常自动终止,不回打印任何异常信息
* 核心参数介绍:
* keepAliveTime:
- 指非核心线程空闲时间达到的阈值时将会被回收。 java核心线程池的回收由allowCoreThreadTimeOut参数控制,默认为false,若开启为true,则此时线程池中不论核心线程还是非核心线程,只要其空闲时间达到keepAliveTime都会被回收。但这样就违背了线程池的初衷(减少线程创建和开销),所以默认该参数为false。
* RejectedExecutionHandler:
- CallerRunsPolicy:会调用当前线程池的所在的线程去执行被拒绝的任务。[最常用,谁也不希望任务被丢失]
- DscardOldestPolicy:弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去最后
- DiscardPolicy:不处理直接丢弃掉任务;
- AbortPolicy: 直接拒绝所提交的任务,并抛出 RejectedExecutionException 异常;【abort 流产,一般指比较严重】
* workQueue:
- synchronousQueue(同步移交队列),队列不作为任务的缓冲方式,可以简单理解为队列长度为零。
- linkedBlockingQueue(无界队列)队列长度不受限制,当请求越来越多时(任务处理速度跟不上任务处理速度造成请求堆积)可能导致内存占用过多或OOM
- ArrayBlockingQueue(有界队列):队列长度受限制
- DelayQueue(无界阻塞延迟队列)队列中每个元素均有过期时间,当从队列获取元素时,只有过期元素才会出队。队头元素时最快要过期的元素。
linkedBlockingQueue 队列源码:
采用生产者消费者模式,并在生产者和消费者两端各加一把锁。
/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
通知机制
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
/**
* Signals a waiting put. Called only from take/poll.
*/
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
加入队列
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
生产者
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//可响应中断,如果收到中断就不等待
putLock.lockInterruptibly();
try {
//队列满了就阻塞
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
//队列有位置了,就唤醒一个线程
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
//队列有数据,则通知消费者消费
if (c == 0)
signalNotEmpty();
}
移除队列
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
消费者
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
* 关闭线程池
- shutDown()
- 平滑关闭线程池,正在执行中的以及队列中的任务能执行完成,后续进来的任务会被执行拒绝策略
- shutdownNow()
- 立刻关闭线程池(暴力),正在执行的以及队列中的任务会被中断,同时该方法会返回被中断队列中的任务列表
* 常见线程池
- 1、使用场景,执行大量短生命周期任务,每次提交任务,都会立刻又线程去处理 IO密集型
- 2、使用场景,周期性执行任务,并且需要限制线程数量的场景。
- 3、创建单个线程,需要保证顺序的执行各个任务,并且在任意时间点不会有多个线程活动的场景
- 4、确保cpu在长期被工作线程使用的情况下,尽可能少的分配线程,适用于长期的任务
- 适用于处理cpu密集型的任务,
*
CompletableFuture,简单会使用,知道有什么。
* 使用线程池规范
- 1、不要使用局部线程池。 使用完之后不关闭,线程会消耗宝贵的系统资源,比如内存等,所以是很不推荐使用局部线程池。 最好放在bean容器里,全局唯一。
- 2、 在创建线程池要业务隔离,为每个线程池指定其名字,方便排查问题。
- 3、 get 操作不设置超时时间,那么主线程一直会被阻塞。
- 4、 @Async 不指定线程池。 如果@Async 不指定线程池,默认是SimpleAsyncTaskExecutor。它不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程,没有最大线程数设置。
- 5、 CompletableFuture 不指定线程池。没有指定线程池默认是ForkJoinPool当CPU核心数-1大于1时,才会使用默认的线程池,否则将会为每个CompletableFuture的任务创建一个新线程去执行,有资源耗尽的风险。 它更适合cpu密集型任务,但是我们平常写业务代码,更多的是IO密集型任务
- 6、 线程池不用ttl 包装
- 7、 线程池参数不能硬套公式,而要可观察可动态配置。
* 使用多线程就要解决原子性、有序性、可见性。
-
1、 原子性
-
在一次或多次操作中,要么所有的操作都执行并且不会受其他因素干扰而中断,要么所有的操作都不执行。
-
编程方面锁优化:
-
1 同步代码中尽量短,减少同步代码的执行时间,减少锁的竞争
-
2将一个锁拆分为多个锁提高并发度
-
3读取时不加锁,写入和删除时加锁
-
2、有序性
-
(1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序;
-
(2)指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序;
-
(3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行的。
-
3、可见性
-
volatile 关键字
-
//保证了指令有序性和可见性。但是没有保证原子性。所以一般在编程的时候很少用到。
-
volatite禁止重排序保证有序性,Java 编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序。
-
volatile 写是在前面和后面分别插入内存屏障,而 volatile 读操作是在后面插入两个内存屏障。
-
在每个 volatile 写操作的前面插入一个 StoreStore 屏障。
-
在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。
-
在每个 volatile 读操作的后面插入一个 LoadLoad 屏障。
-
在每个 volatile 读操作的后面插入一个 LoadStore 屏障。
-
可见性:
-
在 volatile ,缓冲一致协议
-
将当前处理器缓存行的数据写回到系统内存。 写回内存的操作会使在其他 CPU 里缓存了该内存地址的数据无效。
-
不能保证原子性:与voliate只能修饰变量,不能修饰方法有关。
-
用voliate 修饰的i,进行i++能保证原子性吗?不能
-
就i++来说,他包含三个步骤
-
1、 读取i的值
-
2、 给i的值+1
-
3、 把i的值刷新到主内存
CAS乐观锁
-
CAS是一种乐观锁,乐观锁主要就是两个步骤,数据更新和冲突检查
-
CAS操作包含三个操作数,内存中的实际值V,预测原值A,新值B。如果内存的值V与预期原值A相等,那么将内存中的值更新为新值B。如果不相等,那么处理器不会做任何操作,无论哪种情况,它都会在CAS指令之前返回该位置的值。
-
CAS优点:
-
1、由于CAS是非阻塞的,可避免死锁,线程间的互相影响是非常小的。
-
2、没有锁竞争带来系统开销,也没线程程间频繁调度的开销。
-
CAS缺点:
-
1、可能自旋循环时间过长【限制循环次数】
-
2、出现ABA问题【版本号】
-
3、只能对单个变量进行同步
底层用的CMPXCHG,保证原子性。
底层通过 lock前缀指令来保证有序性和可见效。
-
Synchronized
-
Synchronized 即可以修饰方法、代码块,还可以修饰静态方法和类。只不过,Synchronized修饰的静态方法锁定的是这个类的所有对象,修饰的类也是锁定这个类的所有对象。[无论修饰什么,一定要锁共享对象,而不是私有变量]
-
synchronized修饰代码块时原理。它编译成字节码其实是这样的结构,一对指令monitorenter和monitorexit包括着代码逻辑。修饰方法的时候,给方法加上标识
-
ObjectMonitor() {
-
_recursions = 0; // 线程的重入次数_object = NULL; // 存储该monitor的对象
-
_owner = NULL; // 标识拥有该monitor的线程
-
_WaitSet = NULL; // 处于wait状态的线程,会被加入到_WaitSet
-
_cxq = NULL; // 多线程竞争锁时的单向列表
-
_EntryList = NULL; // 处于等待锁block状态的线程,会被加入到该列表
-
}
-
Monitorenter
-
每一个对象都会和一个监视器monitor关联,监视器被占用时会被锁住,其他线程无法来获取monitor,当jvm执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权
-
1、若monitor的进入数为0,线程可以进入monitor,并将monitor的进入数置为1,当前线程成为monitor的owner(所有者)
-
2、若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1
-
3、若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,进入阻塞队列,直到monitor的进入数变为0,才能重新尝试获取monitor的所有权
-
Monitorexit
-
1、能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程
-
2、执行monitorexit时会将monitor的进入数减1,当monitor的进入数减为0时,当前线程退出monitor,不在拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。
-
另外:Monitorexit释放锁,monitorexit插入在方法结束处和异常处。所以出现异常会释放锁。Jvm保证每个monitorenter必须有对应的monitorexit。
-
synchronized 保证可见性,有序性,原子性
-
synchronized保证原子性的原理,synchronized保证只有一个线程拿到锁,能够进入同步代码块。
-
synchronized保证有序性的原理,我们加synchronized后,依然会发生重排序,只不过,我们有同步代码块,可以保证只有一个线程执行同步代码中的代码。保证有序性
-
synchronized保证可见性的原理,执行synchronized时,会对应lock原子操作会刷新工作内存中共享变量的值
-
Synchronized 是重量级锁?
-
无论Synchronized还是Reentrantlock ,线程获取不到锁的阻塞等待,和唤醒,都会涉及到内核态的切换。但是Synchronized 只要想获取锁就要进入内核态,有内核协调那个线程获取锁,所以无论线程获取到锁还是获取不到锁,都会内核态和非内核态切换。但是Reentrantlock 尝试获获取锁,更改status状态不用进入内核态,只有获取不到假如AQS队列时才会park进入内核态。
-
synchronized优化
-
(1) 偏向锁:
-
背景:经过研究实践发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。
-
偏向锁原理:当线程第一次访问同步块并获取锁时,偏向锁处理流程如下:
-
- 虚拟机将会把对象头中的标志位设为“01”,即偏向模式。
-
- 同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中 ,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,偏向锁的效率高。持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,偏向锁的效率高。
-
偏向锁的撤销:
-
好处:
-
偏向锁是在只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。它同样是一个带有效益权衡性质的优化,也就是说,它并不一定总是对程序运行有利,如果程序中大多数的锁总是被多个不同的线程访问比如线程池,那偏向模式就是多余的。
-
(2)轻量级锁原理
-
轻量级锁,当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁
-
1.判断当前对象是否处于无锁状态(hashcode、0、01),如果是,则JVM首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,将对象的Mark Word复制到栈帧中的Lock Record中,将Lock Reocrd中的owner指向当前对象。
-
- JVM利用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,如果成功表示竞争到锁,则将锁标志位变成00,执行同步操作。
-
- 如果失败则判断当前对象的Mark Word是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块;否则只能说明该锁对象已经被其他线程抢占了,这时轻量级锁需要膨胀为重量级锁,锁标志位变成10。
为什么轻量级锁要用栈帧,不像偏向锁那样用cas操作对象头字段,因为偏向锁,是单线程执行。而轻量级锁要多线程交替执行。所以要保护各自的现场。
-
好处:在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗。
-
(3)自旋锁
-
Sychronized 中的monitor会阻塞和唤醒线程,线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自 旋) , 这项技术就是所谓的自旋锁。
-
但是自旋需要消耗cpu,所以需要设置一个最大等待时间,超过了这个时间则停止自旋进入阻塞状态。
-
这些锁优化就是避免线程一上来就进入内核态,只有尝试获取不到锁之后才升级重量级锁。假如获取不到锁了,无论sychronize还是reentranlock 都会进入内核态,因为线程挂起和唤醒都是操作系统层面的,从而避免sychronize一上来就进入内核态成为重量级锁。
-
JVM方面
-
锁消除:虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁会进行消除
-
锁粗化:如果一系列的连续操作都是对同一对象反复加锁和解锁,甚至加锁操作是出现在循环体重,既没有线程竞争,也会产生性能消耗。锁粗化能把加锁同步的范围扩展到整个操作序列的外部,由多次加锁变成只加锁一次
-
ReentrantLock锁
-
AQS原理:
-
是Java层级实现的协调多线程执行同步代码块时,对共享资源的获取,等待,释放的模版类。AQS是基于CLH队列和用Volate修饰共享变量state实现的,通过CAS修改state的状态,getstate,setstate,compare and setstate,改变成功获取锁,失败则请求资源的线程被封装成CLH队列节点,利用CAS放入队列尾部,等待被唤醒,# 其中CLH队列是双链表FIFO
-
同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样,继承AbstractQueuedSynchronizer并重写指定的方法。
-
自定义同步器在实现的时候只需要实现共享资源state的获取和释放方式即可。
至于具体线程等待队列的维护,加入队列,移除队列唤醒线程,AQS已经在顶层实现好了。自定义同步器实现的时候主要实现下面几种方法:
-
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
-
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
-
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
-
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
-
isHeldExclusively 判断当前线程是否持有锁。
-
AQS 定义了两种资源共享方式:
-
1.Exclusive:独占,只有一个线程能执行,只有当前AQS的持有则线程才能操作status变量,如ReentrantLock
-
2.Share:共享,多个线程可以同时可以操作status,如Semaphore、CountDownLatch,CyclicBarrier
-
其中ReentrantReadWriteLock如表特殊,他把status按位切割使用”的方式来维护这个变量,将其切分为两部分:高16为表示读共享,低16为表示写独占。
-
Reentrant可重入
-
ReentrantLock提供了lock()、tryLock()、tryLock(long timeout, TimeUnit unit)、lock.lockInterruptibly()
-
Lock()
-
当锁不可用(变量status不等于0且当前线程不持有锁),那么当前线程被阻塞,进入双链表队列。当锁可用(既status=0 ,利用CAS把status置为1,设置当前线程持有锁 ,或者当前线程持有锁,则把stastu+1,可重入)
-
tryLock()
-
当获取锁时,如果其他线程持有该锁,无可用锁资源,直接返回false,这时候线程不用阻塞等待,不进入双链表对象,可以先去做其他事情;
-
即使该锁是公平锁fairLock,使用tryLock()的方式获取锁也会是非公平的方式,只要获取锁时该锁可用那么就会直接获取并返回true。这种直接插入的特性在一些特定场景是很有用的。
-
tryLock(long timeout, TimeUnit unit)
-
将在指定的时间内尝试获取锁,如果在期间获取到锁,立即返回true,超时则返回false。
-
Unlock
-
把status -1>0 ,则当前线程继续持有该锁,如果status -1=0,则把锁的线程持有则置为空,既当前线程释放锁资源,同时唤醒AQS双向队列的第一个节点,下一个节点获取锁,执行同步代码块。
-
ReentrantLock类里有两个继承AQS的抽象类,一个抽象类fair,另一个unfair,所以它
-
提供了fair和unfair两种模式的锁。默认构造函数是unfair的锁。
-
【
-
公平锁:线程会直接进入队列去排队,不会尝试获取锁,永远都是队列的第一位才能得到锁。
-
非公平锁:会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
-
】
-
ConditionObject 是AQS的内部类,是另一个阻塞等待共享变量。当执行condition.await()时,会构造当前线程的节点的一个node,然后加入队列里。这个队列是一个单相链表。
与AQS队列的区别,AQS是拿锁失败进入队列排队,condition是主动进行队列,先进行锁释放,然后挂起,队列转移。
触发条件:
线程主动调用 await() 方法时,会释放当前持有的锁,并进入条件队列等待。
队列性质:
是一个单向链表(与 AQS 的双向链表不同),存储的是调用 await() 的线程。
线程状态:
线程进入条件队列时,会先释放锁(通过 AQS 的 release 机制),然后进入 WAITING 或 TIMED_WAITING 状态(通过 LockSupport.park() 挂起)。
唤醒机制:
当其他线程调用 signal() 时,从条件队列头部取出一个线程,转移到 AQS 同步队列,等待重新抢锁。
-
Condition. Signal就把单链表中排在第一位的拿出来进行唤醒。
-
Condition. signalAll就把该对象中单链表中的所有节点线程进行唤醒
-
Lock+condition 大致等于sychronized+object.wait/notify
-
Await =wait
-
Signal=notify
-
signalAll=notifyall
-
面试题:synchronized与Lock的区别
-
Lock和synchronized 性能差不多,但是lock更加灵活。
-
- synchronized是关键字,而Lock是一个接口。
-
- synchronized会自动释放锁,而Lock必须手动释放锁。
-
- synchronized是不可中断的,Lock可以中断也可以不中断。
-
- 通过Lock可以知道线程有没有拿到锁,而synchronized不能。
-
- synchronized能锁住方法和代码块,而Lock只能锁住代码块。
-
- synchronized是非公平锁,ReentrantLock可以控制是否是公平锁。
-
ReentrantReadWriteLock,
-
他有两个AQS内部类,fair,unfair。且有两个lock 内部类,读锁,写锁。
-
其中ReentrantReadWriteLock比较特殊,他把status按位切割使用”的方式来维护这个变量,将其切分为两部分:高16为表示读共享,低16为表示写独占。
-
当写锁想获取锁,他必须等到高16为0,且(低16位为0,或者当前线程持有锁),或者就加入AQS队列。实现读写互斥,写写互斥。
-
当读锁想获取锁的时候,低16必须为0 ,并把高16位加1,因为共享变量,所以不用判断当前读锁被谁持有。或则进入等待队列。读写互斥,读读共享
-
读锁写锁的释放,就是把相应的位-1,如果>0 ,则不唤醒CLH队列的头结点,如果=0 则唤醒。
-
在同一个线程中,在没有释放写锁的情况下,就去申请读锁,这属于锁降级。【保证这个线程在执行期间【无论读写锁】,读某个变量的值不被其他线程修改】
-
在同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的
-
为什么不支持锁的升级,假设有a,b 两个线程都拥有读锁,都要升级,a等待b释放读锁,b等待a释放读锁。结果两个线程都不释放读锁。产生一个死锁
-
线程间的通信:
* CountDownLatch:
- 底层原理,CountDownLatch(n),status=n,当执行 await就把线程加入到CHL队列进行阻塞,当每个线程执行执行CountDown,就把共享变量status-1,当=0 的时候就唤醒CHL所有的队列,继续执行同步代码。
- www.cnblogs.com/Lee_xy_z/p/…
* CycliBarrier:
- CyclicBarrier 底层原理未直接使用 AQS,而是通过 ReentrantLock 和 Condition 来实现的. 以下是其底层原理的具体介绍:
- 成员变量
- CyclicBarrier 有两个重要成员变量,parties代表每次拦截的线程数量,count作为计数器,初始值为parties,每有一个线程执行到同步点时,count减 1,当count值变为 0 时,说明所有线程都到达了同步点.
- 等待与阻塞
- 当线程调用await()方法时,会调用dowait()方法,首先通过ReentrantLock上锁,保证对count修改的原子性,然后进行--count操作,接着使用ReentrantLock提供的Condition的await()方法让当前线程阻塞等待,直到count等于 0.
- 执行与唤醒
- 当count为 0 时,说明所有线程都到达了屏障,此时会执行预先定义好的Runnable任务,然后通过nextGeneration()方法唤醒所有等待的线程,并重置计数器count为parties,以便进行下一次的等待和唤醒操作
* Semaphore
- 底层原理,new Semaphore(n)的是给共享变量status=n,然后每个线程在执行acquire,如果发现status=0,则进入CLH阻塞队列。否则会把共享变量-1,继续执行同步代码。
- 当执行完进行rease的时候status+1,会唤醒CLH的头节点继续acquire。
- blog.csdn.net/weixin_6342…
* Exchanger:
-
##################################################################(单线程)as-if-serial语义的意思是:不管编译器和CPU如何重排序,必须保证在单线程情况下程序的结果是正确的。
-
(多线程)JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证
-
Happen-before原则:
-
(1) 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
-
(2) 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
-
(3) volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
-
(4 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
-
(5)start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
-
(6)Join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
-
(7) 程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。
-
(8) 对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。
-
并发工具:
ConcurrentHashMap
一、数据结构与锁机制优化
-
Java 8 的哈希桶锁
- 数组 + 链表/红黑树 结构,锁粒度细化到单个哈希桶(数组元素)。
- 并发度与哈希桶数量成正比(默认初始容量 16),内存更紧凑。
- 核心优势:不同哈希桶的操作完全并发,冲突概率显著降低。
二、put 操作流程与同步设计
1. 插入流程
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 1. 计算哈希,定位哈希桶
int hash = spread(key.hashCode());
Node<K,V>[] tab = table;
// ...
// 2. CAS 无锁插入空桶
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break;
// 3. 哈希桶非空时加锁处理,锁的是桶节点,而非整个桶
synchronized (f) {
// 处理链表或红黑树插入
}
}
2. 锁策略对比
| 结构 | 锁机制 | 并发性 | 适用场景 |
|---|---|---|---|
| 链表 | synchronized 锁链表头节点 | 同一链表操作串行化 | 短链表(冲突概率低) |
| 红黑树 | TreeBin 内部 CAS + 读写锁状态 | 读操作并行,写操作互斥 | 长链表(冲突概率高) |
三、TreeBin 的并发控制
1. 锁状态管理 (lockState)
-
3 位二进制标志:
WAITER(第1位) :是否有线程等待写锁(阻塞唤醒机制)。WRITER(第2位) :写锁是否被占用。READER(第3位起) :读锁计数(允许多个读线程)。
2. 红黑树插入流程
// 竞争写锁
if (U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER)) {
try {
// 插入节点逻辑
} finally {
// 释放锁,唤醒等待线程
releaseLock();
}
} else {
contendedLock(); // 自旋或挂起
}
四、size() 方法的线程不安全问题
1. 实现原理
-
分治计数:
baseCount+CounterCell[]分段统计。 -
源码逻辑:
final long sumCount() { CounterCell[] cs = counterCells; long sum = baseCount; if (cs != null) { for (CounterCell c : cs) sum += c.value; } return sum; }
2. 不安全的本质
- 弱一致性:统计期间可能有并发修改,返回近似值。
- 设计权衡:精确统计需全局锁,牺牲性能换取准确性。
要想精确的size,自己维护一个全局变量,如atomicLong
六、解决方案与代码示例
1. 使用不可变对象(推荐)
public final class ImmutableData {
private final int value; // final 保证构造后可见性
private final String name;
public ImmutableData(int value, String name) {
this.value = value;
this.name = name;
}
// 仅提供 getter,无 setter
}
// 使用方式:每次更新创建新对象
map.put("key", new ImmutableData(2, "new"));
2. 使用原子方法替代复合操作
// 原子操作方法
map.putIfAbsent("key", value);
// 原子计算更新
map.compute("key", (k, oldVal) ->
(oldVal == null) ? newValue : oldVal.update()
);
3. 安全发布对象
public class SafePublisher {
public void init() {
Data data = new FullyConstructedData(); // 确保构造函数完成所有初始化
map.put("key", data);
}
}
4. 避免直接修改已存储对象
map.compute("key", (k, v) -> {
MutableData newData = v.copy();
newData.setValue(100);
return newData;
});
七、底层原理验证
ConcurrentHashMap 通过以下机制保证可见性:
- 哈希桶数组 (
volatile Node[] table) :数组引用用volatile确保扩容时新数组立即可见。 - 节点值 (
volatile V val) :每个链表节点的值字段是volatile的,写入后立即可见。 - 红黑树根节点 (
TreeBin.volatile TreeNode root) :树结构变更通过volatile写保证可见性。
另外:以下可能会产生并发问题,不是原子操作
if(!ConcurrentHashMap.containKey(1)){
ConcurrentHashMap.put(1,1)
}
* CopyOnWriteArrayList,添加和修改类似:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
java 上下文传参数:
ThreadLocal
核心类
-
Thread类
每个线程内部持有一个 ThreadLocal.ThreadLocalMap实例,通过threadLocals` 字段维护线程局部变量。 -
ThreadLocal<T>
用户直接操作的类,是可以操作ThreadLocalMap的工具,提供get()、set(T value)、remove()方法。用户不能直接操作ThreadLocalMap -
ThreadLocalMap
内部静态类,实际存储线程局部变量的容器。Entry(ThreadLocal<?> key, Object value)
关系图
plaintext
Thread
└── threadLocals: ThreadLocalMap
├── Entry[] table
│ └── Entry(ThreadLocal<?> key, Object value)
└── ... (其他方法)
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
为什么用弱引用:
1、假如每个key都强引用指向threadlocal,那么threadlocal就下次垃圾回收就不会被回收。除非线程结束,线程被回收了,map也跟着回收。Threadlocal才会被回收。造成内存泄漏 。
2、弱引用的话,下次垃圾回收,threadlocal 就会被回收,这是map的key就变成了null,但是value不能被回收。这样的话就会造成一个内存泄漏。
在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。
但是这些被动的预防措施并不能保证不会内存泄漏:
使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏。
InheritableThreadLocal
如果想在父子线程进行传递,可以用InheritableThreadLocal;InheritableThreadLocal 也是ThreadLocalMap对象,只不过在线程中名字叫inheritableThreadLocals;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
InheritableThreadLocal之所以能够完成线程间变量的传递,是在new Thread()的时候对
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
// ... 其他初始化逻辑
// 复制父线程的 inheritableThreadLocals
Thread parent = currentThread();
if (parent.inheritableThreadLocals != null) {
// 深拷贝父线程的 inheritableThreadLocals 到子线程
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
}
inheritableThreadLocals对像里的值进行了复制。是浅copy
当父线程通过 InheritableThreadLocal 设置了一个值,并在创建子线程时,子线程会通过 //childValue 方法获取父线程值的副本。默认情况下,子线程直接继承父线程的值,但可以通过重写该方法自定义//传递逻辑。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
// 覆盖 childValue 方法,定义如何将父线程的值传递给子线程
protected T childValue(T parentValue) {
return parentValue;
}
// 重写 getMap 和 createMap,操作的是 Thread 类的 inheritableThreadLocals 字段
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
修改传递值:
public class CustomInheritableThreadLocal<T> extends InheritableThreadLocal<T> {
@Override
protected T childValue(T parentValue) {
// 自定义逻辑:在父值基础上添加前缀
return (T) ("Child-Value-" + parentValue);
}
}
TransmittableTreadLocal 源码分析
继承InheritableThreadLocal,
public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> implements TtlCopier<T>
值也是放入到inheritableThreadLocals 字段的 ThreadLocalMap
set(),get()本质也是InheritableThreadLocal set(),get() 也即是ThreadLocal的set(),get(),但是在set的时候增加了addThisToHolder()参数
InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder
@Override
public final T get() {
T value = super.get();
if (disableIgnoreNullValueSemantics || null != value) addThisToHolder();
return value;
}
/**
* see {@link InheritableThreadLocal#set}
*/
@Override
public final void set(T value) {
if (!disableIgnoreNullValueSemantics && null == value) {
// may set null to remove value
remove();
} else {
super.set(value);
addThisToHolder();
}
}
holder 用于跟踪所有需要传递的TransmittableThreadLocal实例。
private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
}
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
}
};
把当前的 TransmittableThreadLocal 放入到holder 方便后续遍历使用; ThreadLocalMap 存储了线程所有 ThreadLocal 变量,但其中可能包含大量与 TTL 无关的键值对。每次传递变量时都需要遍历整个 ThreadLocalMap,并通过 instanceof 检查每个 key 是否为 TransmittableThreadLocal 类型,时间复杂度为 O(n),效率低下。
private void addThisToHolder() {
if (!holder.get().containsKey(this)) {
holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
}
}
包装原始线程:
ExecutorServiceTtlWrapper(包装原来的ExecutorService,只是为了在执行的多了wrapper动作)
ExecutorServiceTtlWrapper ttlExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(4));
主线程在submit 的时候包装之前的Runnable,生成TtlRunnable;wrapper,run操作。
class ExecutorServiceTtlWrapper extends ExecutorTtlWrapper implements ExecutorService, TtlEnhanced {
private final ExecutorService executorService;
@NonNull
@Override
public <T> Future<T> submit(@NonNull Runnable task, T result) {
return executorService.submit(
TtlRunnable.get(task, false, idempotent), result);
}
}
@Nullable
public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
if (null == runnable) return null;
if (runnable instanceof TtlEnhanced) {
// avoid redundant decoration, and ensure idempotency
if (idempotent) return (TtlRunnable) runnable;
else throw new IllegalStateException("Already TtlRunnable!");
}
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
TtlRunnable--》new TtlRunnable()
TtlRunnable
在生成TtlRunnable的时候,会capture()当前主线线程的TTL值放入Snapshot对行,capturedRef指向这个Snapshot地址引用;new AtomicReference(capture());
public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments {
private final AtomicReference<Object> capturedRef;
private final Runnable runnable;
private final boolean releaseTtlValueReferenceAfterRun;
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference<Object>(capture());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
/**
* wrap method {@link Runnable#run()}.
*/
@Override
public void run() {
final Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
final Object backup = replay(captured);
try {
runnable.run();
} finally {
restore(backup);
}
}
}
捕获当前线程的TTL值
public static Object capture() {
return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}
private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
ttl2Value.put(threadLocal, threadLocal.copyValue());
}
return ttl2Value;
}
private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
final ThreadLocal<Object> threadLocal = entry.getKey();
final TtlCopier<Object> copier = entry.getValue();
threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
}
return threadLocal2Value;
}
private static class Snapshot {
final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value;
final HashMap<ThreadLocal<Object>, Object> threadLocal2Value;
private Snapshot(HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value, HashMap<ThreadLocal<Object>, Object> threadLocal2Value) {
this.ttl2Value = ttl2Value;
this.threadLocal2Value = threadLocal2Value;
}
}
RUN方法
执行Run方法的时候已经是线程池中的线程了
public void run() {
//拿到主线程的值
final Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
//保留线程池的上线文信息,把主线程的值放到现场池线程的上下文里。
final Object backup = replay(captured);
try {
runnable.run();
} finally {
//恢复线程池中线程的上线文信息
restore(backup);
}
}
replay 方法
@NonNull
public static Object replay(@NonNull Object captured) {
final Snapshot capturedSnapshot = (Snapshot) captured;
return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
}
@NonNull
private static HashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> captured) {
HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap<TransmittableThreadLocal<Object>, Object>();
for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
TransmittableThreadLocal<Object> threadLocal = iterator.next();
// backup 备份线程池中线程的TTL值
backup.put(threadLocal, threadLocal.get());
// clear the TTL values that is not in captured
// avoid the extra TTL values after replay when run task
if (!captured.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
// set TTL values to captured
//把主线程的值设置到当前线程TTL值
setTtlValuesTo(captured);
// call beforeExecute callback
doExecuteCallback(true);
//返回 当前线程的TTL值
return backup;
}
restore 方法
把原来的值在设置到当前线程池里的TTL里
public static void restore(@NonNull Object backup) {
final Snapshot backupSnapshot = (Snapshot) backup;
restoreTtlValues(backupSnapshot.ttl2Value);
restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
}
private static void restoreTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> backup) {
// call afterExecute callback
doExecuteCallback(false);
for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
TransmittableThreadLocal<Object> threadLocal = iterator.next();
// clear the TTL values that is not in backup
// avoid the extra TTL values after restore
if (!backup.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
// restore TTL values
setTtlValuesTo(backup);
}