1 死锁
1 死锁概念:两个线程在拥有锁的情况下,又在尝试获取对方锁
2 死锁产生的必要因素:
-
互斥条件:一个资源同一时间只能被一个线程占用,当这个资源被占用后其他线程只能等待
-
不可剥夺条件:一个资源被占有后,如果不是拥有资源的线程主动释放,其他线程不可用由此资源
-
请求并持有条件:当一个线程拥有一个资源是不满足,并且申请其他资源
-
环路等待条件:多个线程在请求资源的情况下,形成了环路链
以上四个条件是导致死锁的必要条件,四个条件缺一不可
3 解决死锁:改变产生死锁原因中的任意⼀个或多个条件就可以解决死锁的问题,其中可以被修改的条件只有两个:请求并持有条件 和 环路等待条件。
-
请求并持有条件(可人为控制
1 不去拥有不可抢占资源
public class ThreadDead {
public static void main(String[] args) {
Object lockA=new Object();
Object lockB=new Object();
Thread t1=new Thread(()->{
synchronized (lockA){
System.out.println("线程1获取到锁A");
//空档期 让对方先得到锁B
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// synchronized (lockB){
// System.out.println("线程1得到锁B");
// System.out.println("线程1释放锁B");
// }
System.out.println("线程1释放锁A");
}
});
t1.start();
Thread t2=new Thread(()->{
synchronized (lockB){
System.out.println("线程2获取到锁B");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// synchronized (lockA){
// System.out.println("线程2得到锁A");
// System.out.println("线程2释放锁A");
// }
System.out.println("线程2释放锁B");
}
});
t2.start();
}
}
线程1得到锁A
线程2得到锁B
线程2得到锁B
线程1得到锁A
-
破坏环路等待条件(可人为控制
按照已经编好的序号顺序执行
public class UnDeadLock2 {
public static void main(String[] args) {
Object lockA = new Object();
Object lockB = new Object();
Thread t1 = new Thread(() -> {
synchronized (lockA) {
System.out.println("线程1得到锁A");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println("线程1得到锁B");
System.out.println("线程1释放锁B");
}
System.out.println("线程1释放锁A");
}
}, "线程1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (lockA) {
System.out.println("线程2得到锁A");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println("线程2得到锁B");
System.out.println("线程2释放锁B");
}
System.out.println("线程2释放锁A");
}
}, "线程2");
t2.start();
}
}
线程1得到锁A
线程1得到锁B
线程1得到锁B
线程1得到锁A
线程2得到锁A
线程2得到锁B
线程2得到锁B
线程2得到锁A
2. 线程通讯
1 主要方法:(对象级别 都是obj内置方法)
-
wait()/wait(long timeout)
:线程进入休眠状态 -
notify()
: 随机唤醒休眠线程 -
notifyAll()
:唤醒所有处于休眠状态的线程
注意这三个⽅法都需要配合 synchronized ⼀起使⽤
2 wait使用:
synchronized (lock){// 2.synchronized wait 必须配合使用
System.out.println("线程1开始执行wait方法");
lock.wait();// 1.lock对象必须是同一个
}
-
wait 执⾏流程
1 使当前执⾏代码的线程进⾏等待. (把线程放到等待队列中) eg:xxx.wait()
2 释放当前的锁
3 满⾜⼀定条件时被唤醒, 重新尝试获取这个锁 -
wait 结束等待的条件:
1 其他线程调⽤该对象的 notify ⽅法.
2 wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间).eg:wait(XX)
3 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常
3 notify使用:
- notify调用后并不是立马执行唤醒线程操作,而是等notify中的synchronized执行完(释放完锁),才能去唤醒操作;
4 notifyAll使用:同一把锁,随机唤醒所有休眠线程,其他同上
3 线程通讯注意事项:
1
-
wait /notify /notifyAll 必须要配合synchronized一起使用(保证执行顺序,防止混乱(比如wait notify顺序颠倒就会执行异常))
-
wait /notify /notifyAll 进行synchronized加锁,一定要使用同一个对象加锁
-
当调用wait /notify /notifyAll之后,程序不会立马执行唤醒操作,而是尝试获取锁,只有得到锁之后才能继续执行
-
notifyAll 并不是唤醒所有的wait等待的线程 而是唤醒当前对象处于wait等待的线程
2 wait() VS wait(long timeout):
-
不同点:
1 wait(long timeout):当线程执行到达设置的时间后自动恢复执行
wait() 无限等待状态2 调用wait(long timeout):TIMED_WAITING ;调用wait():WAITING;
-
共同点
1 无论是有参还是无参的等待方法,都可以使得当前线程进入休眠状态;
2 无论是有参还是无参的等待方法,都可以使用notify/notifyAll进行唤醒
3 wait(0)VS sleep(0)
- wait(0):无限期等待
- sleep(0):相对于Thread.yeild(),让出cpu执行权,重新调度,sleep(0)会继续执行
4 wait sleep 在有有锁情况下释放锁的行为
- wait方法不管是有参的还是无参的在执行的时候都会释放锁,而sleep方法不会释放锁
5 wait VS sleep
-
相同点:
1 都可以让线程进入休眠状态
2 都可以响应interrupte(中断)请求
-
不同点:
1 wait 必须在 synchronized 中使⽤,⽽ sleep 却不⽤;
2 sleep 是 Thread 的⽅法,⽽ wait 是 Object 的⽅法;
3 sleep 不释放锁,wait 释放锁;
4 sleep 有明确的终⽌等待时间,⽽ wait 有可能⽆限期的等待下去;
5 sleep 和 wait 产⽣的线程状态是不同的,sleep 是 TIMED_WAITING 状态,⽽ wait 是 WAITING 状态
6 一般情况下 sleep只能等待超时时间后执行 而wait可以接受notify/notifyAll的唤醒
4 线程休眠和指定唤醒:LockSupport
1 小tips:线程休眠其他方法:
- sleep
- TimeUnit
- wait/notify/notifyAll
- LockSupport. park/umpark(thread)
2 主要方法(LockSupport ⽆需配和 synchronized 使⽤ )
-
LockSupport.park():休眠当前线程。
-
LockSupport.unpark(线程对象):唤醒某⼀个指定的线程
-
LockSupport.parkUntil(long) :等待最⼤时间是⼀个固定时间
LockSupport.parkUntil(System.currentTimeMillis()+3000); 休眠3s后执行自动恢复执行
5 线程池:
传统线程的缺点:
- 每次都要创建和消耗线程,需要消耗系统资源。
- 线程没有任务管理的功能,当任务量较大的时候没有任务队列对任务进行管理或拒绝任务。
1 线程池基本概念:
-
定义:线程池(ThreadPool)是⼀种基于池化思想管理和使⽤线程的机制。
-
池化思想在计算机的应⽤也⽐较⼴泛:
内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎⽚。
连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
实例池(Object Pooling):循环使⽤对象,减少资源在初始化和释放时的昂贵损耗。 -
优点:
1 降低资源消耗 :复用线程,降低线程创建和销毁造成的损耗。
2 提⾼响应速度:任务到达时,⽆需等待线程创建即可⽴即执⾏。
3 提高线程可管理性 :控制线程的数量,防止因为无限制创建线程导致的资源损耗问题,提高系统稳定性,使线程可以进程统一分配,调优和监控,可以实现任务缓存和任务拒绝等情况。
4 线程池提供了更多功能:线程池具备可拓展性 ,⽐如延时定时线程ScheduledThreadPoolExecutor,就允许任务延期执⾏或定期执⾏
2 线程池的使用:
-
两种实现方式:
1 通过ThreadPoolExecutor 手动创建线程池(1种实现方法
2 通过Executors手动创建线程池(6种实现方法 -
七种实现方法:
1 Executors.newFixedThreadPool:创建⼀个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
submit(new Runnable() 可以执行有返回值或没返回值的任务
execute(new Runnable() 只能执行没返回值的任务
2 Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
根据任务数创建线程池 并在一定时间内可以重复使用线程适用于短时间内大量任务的线程 缺点是可能会占用用很多资源
3 Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
-
单线程的线程池有什么意义
1 复用线程
2 提供任务队列(忙时放)和拒绝策略(满时拒绝)
4 Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
-
.scheduleAtFixedRate()
//2秒后开始执行定时任务 定时任务每隔4秒后执行一次 固定时间 // (以上一次任务的开始时间作为延时任务的间隔的开始时间) // 延时和任务执行时间哪个大就以哪个时间作为间隔时间 service.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("任务执行:"+LocalDateTime.now()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } },2,4,TimeUnit.SECONDS);
-
.scheduleWithFixedDelay()
//2秒之后开始执行 任务完成后 再过4秒开始执行任务 //(以上一次任务的结束时间作为延时任务的间隔的开始时间) service.scheduleWithFixedDelay(new Runnable() { @Override public void run() { System.out.println("执行时间:"+LocalDateTime.now()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } },2,4,TimeUnit.SECONDS);
5 Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
6 Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】
根据当前系统配置生成线程数
7 ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置
ThreadPoolExecutor executor=new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), factory, new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("我执行了自定义拒绝策略"); } }); 1.核心线程数 2.最大线程数 3.存活时间(临时员工可闲置存活时间 ) 4.针对3的描述 5.存放阻塞任务的队列 6.线程工厂 7.拒绝策略管理器(处理极端任务 任务队列满了的措施)
3 线程池重要执行节点:
-
当任务来了之后,判断线程池中实际线程数是否小于核心线程数,如果小于直接创建线程并执行任务
-
当实际线程数大于核心线程数,他会判断任务队列是否已满,未满直接将任务放队列即可
-
判断线程池的实际线程数是否大于最大线程数,如果小于最大线程数会直接创建线程执行任务;实际线程数已经等于最大线程数,会直接执行就拒绝策略
4 线程池拒绝策略:(4【jdk提供的拒绝策略】1【自定义】)
- 忽略最新的任务
- 提示异常,拒绝执行(默认)
- 使用调用线程池的线程来执行任务
- 忽略旧任务(任务队列第一个队伍)
- 自定义:
new RejectedExecutionHandler(){};
5 线程池状态:
-
running:线程池创建之后的状态,此状态下可执行任务
-
shutdown:该状态下线程池不再接受新的任务,但是会将工作队列的任务执行完
-
stop:该状态下线程池不再接受新的任务,会将工作队列的任务执行完,会中断线程
-
tidyong:该状态下所有任务终止,将会执行terminated()钓子方法
-
terminated:执行完terminated()调子方法后
shutdown 执⾏时线程池终⽌接收新任务,并且会将任务队列中的任务处理完;
shutdownNow 执⾏时线程池终⽌接收新任务,并且会给终⽌执⾏任务队列中的任务。