线程
线程是并发编程的基础
线程池的状态
线程池一共有6种状态
| 状态名称 | 含义 | 典型场景 |
|---|---|---|
| NEW (新建) | 线程已被创建,但尚未启动(未调用start()方法) | Thread thread = new Thread(); |
| RUNNABLE (可运行) | 线程正在JVM中执行或正准备执行,但可能正在等待操作系统分配CPU时间片 | 调用start()后,等待CPU调度或正在运行 |
| BLOCKED (阻塞) | 线程正在等待获取一个监视器锁(Monitor Lock),以便进入同步代码块或方法 | 竞争synchronized锁时未能立即获取成功 |
| WAITING (等待) | 线程进入无限期等待,直到被其他线程显式唤醒。此状态不占用CPU时间片 | 调用Object.wait(), Thread.join(), LockSupport.park() |
| TIMED_WAITING (超时等待) | 线程进入有时间限制的等待。时间到期后会自动唤醒,或在等待期间被其他线程唤醒 | 调用Thread.sleep(long millis), Object.wait(long timeout), Thread.join(long millis) |
| TERMINATED (终止) | 线程已经执行完毕(run()方法正常退出或因异常而终止) | run()方法执行结束 |

wait() 和 sleep() ⽅法的区别
来源不同
sleep() 来⾃ Thread 类,wait() 来⾃ Object 类。
对于同步锁的影响不同
sleep() 不会该表同步锁的⾏为,如果当前线程持有同步锁,那么 sleep 是不会让线程释放同步锁的。wait() 会释放同步锁,让其他线程进⼊ synchronized 代码块执⾏。
使⽤范围不同
sleep() 可以在任何地⽅使⽤。wait() 只能在同步控制⽅法或者同步控制块⾥⾯使⽤,否则会抛 IllegalMonitorStateException。
恢复⽅式不同
两者会暂停当前线程,但是在恢复上不太⼀样。sleep() 在时间到了之后会重新恢复;wait() 则需要其他线程调⽤同⼀对象的 notify()/nofityAll() 才能重新恢复。
线程的 sleep() ⽅法和 yield() ⽅法有什么区别?
线程执⾏ sleep() ⽅法后进⼊超时等待(TIMED_WAITING)状态,⽽执⾏ yield() ⽅法后进⼊就绪(READY)状态。
sleep() ⽅法给其他线程运⾏机会时不考虑线程的优先级,因此会给低优先级的线程运⾏的机会;
yield() ⽅法只会给相同优先级或更⾼优先级的线程以运⾏的机会。
编写多线程程序有⼏种实现⽅式?
- 实现thread
- 实现runnable
- 实现callback
var t = new Thread() {
@Override
public void run() {
//....
}
};
var t2 = new Thread(() -> {
//....
});
ExecutorService tpe=Executors.newFixedThreadPool(1);
Callable<Integer> c3 = () -> 1;
tpe.submit(c3);synchronized 和 Lock 的区别
1)Lock 是⼀个接⼝;synchronized 是 Java 中的关键字,synchronized 是内置的语⾔实现;
2)Lock 在发⽣异常时,如果没有主动通过 unLock() 去释放锁,很可能会造成死锁现象,因此使⽤Lock 时需要在 finally 块中释放锁;synchronized 不需要⼿动获取锁和释放锁,在发⽣异常时,会动释放锁,因此不会导致死锁现象发⽣;
3)Lock 的使⽤更加灵活,可以有响应中断、有超时时间等;⽽ synchronized 却不⾏,使⽤synchronized 时,等待的线程会⼀直等待下去,直到获取到锁;
4)在性能上,随着近些年 synchronized 的不断优化,Lock 和 synchronized 在性能上已经没有很明显的差距了,所以性能不应该成为我们选择两者的主要原因。官⽅推荐尽量使⽤ synchronized,除⾮ synchronized ⽆法满⾜需求时,则可以使⽤ Lock。
为什么说 synchronized 是⼀种悲观锁?乐观锁的实现原理⼜是什么?什么是CAS,它有什么特性?
synchronized 显然是⼀个悲观锁,因为它的并发策略是悲观的:不管是否会产⽣竞争,任何的数据操作都必须加锁,⽤⼾态核⼼态转换,维护锁计数器和检查是否被阻塞的线程需要被唤醒等操作
乐观锁的实现原理:我们可以使⽤基于冲突检测的乐观并发策略。先进⾏操作,如果没有其他线程征⽤数据,那么就操作成功了;
乐观锁的核⼼算法是 CAS(Compareand Swap,⽐较并交换),它涉及到三个操作数:内存值、预期值、新值。并且仅当预期值和内存值相同时才将内存值修改为新值。
CAS 具有原⼦性,它的原⼦性由 CPU 硬件指令实现保证,即使⽤ JNI 调⽤ Native ⽅法调⽤由 C++ 编写的硬件级指令,JDK中提供了 Unsafe 类执⾏这些操作。
synchronized 各种加锁场景的作⽤范围
public synchronized void method() {} //当前实例作为对象锁
public static synchronized void method() {} //当前类作为对象锁
synchronized (Lock.class) {} //制定的类作为对象锁
synchronized (this) {} //当前实例作为对象锁,特别注意,this在静态方法中不存在
public static Object monitor = new Object(); //指定实例作为对象锁
synchronized (monitor) {}死锁
死锁产生
互斥条件:进程对所分配到的资源进⾏排他性控制,即在⼀段时间内某资源仅为⼀个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
占有等待:进程已经获得了⾄少⼀个资源,但⼜对其他资源发出请求,⽽该资源已被其他进程占有,此时该进程的请求被阻塞,但⼜对⾃⼰获得的资源保持不放。
不可被剥夺:进程已获得的资源在未使⽤完毕之前,不可被其他进程强⾏剥夺,只能由⾃⼰释放。
循环等待:存在⼀种进程资源的循环等待链,链中每⼀个进程已获得的资源同时被 链中下⼀个进程所请求。
预防死锁
互斥条件:在系统⾥取消互斥。若资源不被⼀个进程独占使⽤,那么死锁是肯定不会发⽣的。例如:共享数据变成线程独有。StringBuilder代替StringBuffer
占有等待:1)采⽤资源预先分配策略,即进程运⾏前申请全部资源,满⾜则运⾏,不然就等待。 2)每个进程提出新的资源申请前,必须先释放它先前所占有的资源。
不可被剥夺:当进程占有某些资源后⼜进⼀步申请其他资源⽽⽆法满⾜,则该进程必须释放它原来占有的资源。
循环等待:实现资源有序分配策略,将系统的所有资源统⼀编号,所有进程只能采⽤按序号递增的形式申请资源。
Synchronized锁的膨胀升级过程
Synchronized(同步锁)可以根据情况升级锁,以提高性能

同步锁数据主要存储在对象头中
偏向锁:在⼤多数情况下,锁不仅不存在多线程竞争,⽽且总是由同⼀线程多次获得,因此为了减少同⼀线程获取锁(会涉及到⼀些CAS操作,耗时)的代价⽽引⼊偏向锁。
轻量级锁:JVM会利⽤CAS尝试把对象原本的Mark Word更新回Lock Record的指针,成功就说明加锁成功,于是改变锁标志位,执⾏相关同步操作。如果失败了,判断当前对象的Mark Word是否指向当前线程的栈帧,如果是就表⽰当前线程已经持有该对象锁。如果不是,说明当前对象锁被其他线程持有,于是进⾏⾃旋。
重量级锁:若cas自选失败,会将锁升级成重量级锁,获取锁必须强制等待才能获取锁。
JUC
AQS
AQS(AbstractQueuedSynchronizer)定义了⼀套多线程访问共享资源的同步器框架,是⼀个依赖状态的同步器。AQS定义了很多并发中的⾏为,⽐如:
• 阻塞等待队列
• 共享/独占
• 公平/⾮公平
• 可重⼊
• 允许中断
ReentrantLock介绍
ReentrantLock是基于AQS框架实现的锁,它类似于Synchronized互斥锁,可以保证线程安全。基于
AQS强⼤的并发特性和处理多线程的能⼒,ReentrantLock相⽐Synchronized,拥有更多的特性,⽐
如⽀持⼿动加锁、解锁,⽀持公平锁等。