JUC
# JUC(java.util.concurrent)
# 线程
# 线程状态

- NEW(新建):线程对象被创建后,但还没有调用start()方法
- RUNNABLE(可运行):线程对象被创建后,调用start()方法,线程处于就绪状态
- READY(就绪):线程对象被创建后,调用start()方法,线程处于就绪状态
- RUINNING(运行):线程正被操作系统的调度执行,此时若调用yied()方法,只是谦让出时间片,具体是否让出取决于cpu
- BLOCKED(阻塞):线程正在等待锁的释放,以便进入同步代码块/方法
- WATING(等待):线程处于等待状态,调用wait()方法或者LockSupport.park()方法,进入等待池,等待其他线程调用notify()方法唤醒
- TIME_WAITING(超时等待):线程调用了带有时间限制的Threed.sleep(long)或者Object.wait(long),或者Thread.join(long)或者LockSupport.parkNanos(long)或者LockSupport.parkUntil(long),进入超时等待,等待指定时间后自动唤醒
- TERMINATED(终止):线程执行完毕或因异常而结束,不再处于活动状态
# 新启动线程的方式
- 继承Thread类,重写run()方法
- 实现Runnable接口,重写run()方法
- 实现Callable接口,重写call()方法
- 线程池
官方说法Java中有两种方式来创建线程,一种是继承Thread类,另一种是实现Runnable接口。
# 有T1,T2,T3三个线程,怎样保证T2在T1后,T3在T2后
- join():把指定线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行,比如在线程B中调用了线程A的join()方法,直到线程A执行完毕后才会继续执行线程B剩下的代码
- 用Thread#join
- 在T3中调用T2的join()方法
- 在T2中调用T1的join()方法
# JMM,Java内存模型
mian内存:所有线程共享的内存,包括堆内存和栈内存 线程私有内存:每个线程都有自己私有的内存,包括程序计数器、虚拟机栈和本地方法栈 线程执行时,会从主内存中读取数据到线程私有内存中,执行后将结果写入到主内存中
# 内存可见性
- 可见性:一个线程修改了共享变量的值,其他线程能够立即看到修改的值
# 原子性
- 原子性:一个操作或多个操作,要么全部执行,要么全部不执行
# 有序性
- 有序性:程序执行的顺序按照代码的先后顺序执行
# 等待/通知机制
# Thread
- Thread.yied():让出时间片,让其他线程运行,不会释放锁
- Thread.sleep(long):休眠指定时间,不会释放锁
- Thread.join():等待其他线程执行完毕,不会释放锁
# Object
- 线程A调用了对象O的wait()方法,另一个线程B调用了对象O的notify()方法或者notifyAll(),线程A收到通知后从对象O的wait()方法返回
- 等待,Object.wait(),释放当前线程持有的锁,唤醒后重新抢锁
- 通知,Object.notifyAll()
- 强制在同步块中调用,否则抛出IllegalMonitorStateException
# Lock.Condition
- condition.await():释放当前线程持有的锁,唤醒后重新抢锁
- conditon.signal():唤醒一个等待的线程
# LockSupport
- LockSupport.park():释放当前线程持有的锁,唤醒后重新抢锁
- 调用LockSupport.unpark(Thread)方法,唤醒指定线程
# 范式
- 等待方
加锁(对象){
while(条件不满足){
对象.wait()
}
进入后面的业务逻辑
}
1
2
3
4
5
6
2
3
4
5
6
- 通知方
加锁(对象){
加锁(对象){
业务逻辑,改变条件
对象.notify()
}
1
2
3
4
5
2
3
4
5
# CompeletableFuture
- 使用Runable或Callable实例来提交任务
- 调用get()方法,阻塞等待任务执行完成
- 调用join()方法,阻塞等待任务执行完成
- runAsyn()方法,提交任务后立即返回void
- supplyAsync()方法,提交任务后立即返回值
- anyOf()方法,多个任务中任意一个执行完成就返回
- allOf()方法,多个任务全部执行完成才返回
# volatile
# volatile是JVM提供的轻量级的同步机制
- 保证可见性
- 禁止指令重排
- 不保证原子性
# 在哪些地方用过volatile
- 单例模式DCL代码
public class Singleton{
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if (null == instance){
synchronized(Singleton.class){
if(null == instaince){
instance = new Singleton();
}
}
}
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# CAS(比较并交换)
- 预期值(expected value),当前值(current value,内存中的值),新值(new value),检测当前值是否与预期值相同,如果是,则将当前值更新为新值。compareAndSet(originalValue=expected Value,newValue)
- 底层原理,UnSafe类中的compareAndSwapInt()方法
- CAS的缺点
- 循环时间长,开销大
- 只能保证一个共享变量的原子操作
- 会引出ABA问题
# synchronized
# monitor机制
ObjectMonitor() {
_header = NULL; //对象头 markOop
_count = 0;
_waiters = 0,
_recursions = 0; // 锁的重入次数
_object = NULL; //存储锁对象
_owner = NULL; // 标识拥有该monitor的线程(当前获取锁的线程)
_WaitSet = NULL; // 等待线程(调用wait)组成的双向循环链表,_WaitSet是第一个节点
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //多线程竞争锁会先存到这个单向链表中 (FILO栈结构)
FreeNext = NULL ;
_EntryList = NULL ; //存放在进入或重新进入时被阻塞(blocked)的线程 (也是存竞争锁失败的线程)
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 锁对象
# 内存结构

- 对象头
- Mark Word(存储对象的HashCode,分代年龄和锁标志位信息)
- Class Pointer(指向对象的类元数据,虚拟机通过这个指针来确定这个对象是哪个类的实例)
- Moenitor(指向对象的锁)
- EntryList
- Owner(会指向持有Monitor对象的线程)
- WaitSet
- 实例数据
- 填充数据
# jvm对象描述结构
- 32位

- 64位

# 作用域
- 方法:ACC_SYNCHRONIZED
- 代码块:monitorenter和monitorexit
# 锁对象转换

- 无锁
- 偏向锁
- Mark Word中有线程信息cas比较
- 升级,
- 没有位置保存hashCode,所以在其中调用对象的hashCode()方法会导致锁撤销
- 处于可偏向的时候,调用obj.hashCode(),Mark Word将变成未锁定状态,升级为轻量级锁
- 处于已偏向的时候,调用obj.hashCode(),升级为重量级锁
- notify/wait
- obj.notify(),升级为轻量级锁
- obj.wait(),升级为重量级锁
- 没有位置保存hashCode,所以在其中调用对象的hashCode()方法会导致锁撤销
- 锁撤销
- 轻量级锁
- 复制了一份Mark Word到线程栈中,CAS比较
- 轻微竞争场景
- 同一时间多个线程竞争锁,升级为重量级锁
- 重量级锁
- 复制一份Mark Word到Monitor中,指向monitor对象
# synchronied和Lock区别
- synchronied是jvm实现的锁,Lock是JDK实现的锁
- synchronied是自动释放锁,Lock需要手动释放
- synchronied是非公平锁,Lock可以设置公平锁
- synchronied锁住方法和代码块,Lock锁住代码块
# AQS(AbstractQueuedSynchronizer)
- AQS
- CLH
- 变种CLH,将每条请求共享变量的线程封装成一个节点
- volatile int state
- 变种的CLH双向队列
# Lock
# ReentrantLock
- NonfairSync
- tryAcquire
- acquireQueued
- CAS
- 可能会授予刚刚请求它的线程,而不考虑等待时间
- FairSync
- hasQueuedPredecessors
- 会授予等待时间最长的线程
# ReentrantReadWriteLock
- ReadLock
- WriteLock
# StampedLock
# CountDownLatch/CyclicBarrier/Semaphore
# CountDownLatch
- 只有一个构造方法 只会被赋值一次
- 没有别的方法可以修改 count
- 让一些线程阻塞直到另一些线程完成操作后才被唤醒(班长关门)
- 主要有两个方法,await方法会被阻塞。countDown会让计数器-1,不会阻塞。将计数器变为0时,调用await方法的线程会被唤醒,继续执行。
- 相当于CompletableFuture.allOf()
- CountDownLatchDemo
public class CountDownLatchDemo {
//6个同学陆续离开教室之后,班长锁门
public static void main(String[] args) throws InterruptedException {
//创建CountDownLatch对象,设置初始值
CountDownLatch countDownLatch = new CountDownLatch(6);
//6个同学陆续离开教室之后
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");
//计数 -1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# CyclicBarrier
- CyclicBarrie字面上就是可循环使用的屏障。当一组线程得到一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才会继续工作。进入屏障通过await方法。
- CyclicBarrierDemo
//集齐7颗龙珠就可以召唤神龙
public class CyclicBarrierDemo {
//创建固定值
private static final int NUMBER = 7;
public static void main(String[] args) {
//创建CyclicBarrier
CyclicBarrier cyclicBarrier =
new CyclicBarrier(NUMBER,()->{
System.out.println("*****集齐7颗龙珠就可以召唤神龙");
});
//集齐七颗龙珠过程
for (int i = 1; i <=7; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");
//等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Semaphore
- 信号量主要用于两个目的,一个是多个共享资源的互斥使用,一个是并发线程数的控制。
- SemaphoreDemo
//6辆汽车,停3个车位
public class SemaphoreDemo {
public static void main(String[] args) {
//创建Semaphore,设置许可数量
Semaphore semaphore = new Semaphore(3);
//模拟6辆汽车
for (int i = 1; i <=6; i++) {
new Thread(()->{
try {
//抢占
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" 抢到了车位");
//设置随机停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName()+" ------离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 线程池
# 线程池的种类
- newFixedThreadPool
- newCacheThreadPool
- newSIngleTheadExecutor
- newScheduledThewadPool
- newWorkStealingPool
- ThreadPoolExecutor
# ThreadPoolExecutor
- 核心线程数
- 最大线程数
- 空闲时间
- 空闲时间单位
- 缓冲队列
- LinkedBlockingQueue,无界 当心内存溢出
- ArrayBlockingQueue,有界队列,加锁保证安全 一直死循环阻塞 队列不满就唤醒
- Synchronous
- 工厂方法
- 拒绝策略
- 抛异常:AbortPolicy(默认):直接抛出RejectedException。
- 丢弃:DiscardPolicy:直接丢弃任务,不做任何处理。
- 丢弃最早提交的:DiscardOldestPolicy:丢弃等待最久的任务。
- 而是回退给调用者:CallerRunsPolicy:不会抛弃任务也不会抛出异常,而是回退给调用者
# ThreadPoolExecutor执行过程
核心线程->队列->最大线程->拒绝策略
# 线程池死锁
- 概念:使用线程池处理任务的时候,由于任务之间相互依赖,资源争用或者其他同步问题,导致所有线程都在等待彼此释放资源而无法继续执行的一种状态。
# ThreadLocal
# 使用
- set()
- get()
- remove()
# 给每个线程提供一个独立副本,与线程强关联,跨方法进行参数传递
# 为什么用
- 每个线程都保存一份数据,不会造成数据污染
# 实现
- 设想1:synchronized map(thread,T)
- 实际实现:不只一个T,ThreadLoalMap,定义在ThreadLocal,从Thread声明ThreadLocal.ThreadLocalMap

# 用例
- jdbc原生
- 连接池,事务,多个sql可能不在同一个连接,解决方式一、传递conn
- spring,事务管理器用ThreadLocal存连接
- 微服务,链路追踪
- 动态数据源,用来保存当前线程应该使用哪个数据源
# 问题
- 内存泄漏,ThreadLocalMap的key是弱引用,如果线程没有强引用指向ThreadLocal,那么就会回收,但是value是强引用,如果value没有回收,就会造成内存泄漏
编辑 (opens new window)
上次更新: 2024/06/01, 00:32:42


