1.死锁——在多线程(两个或者两个以上的线程)编程中,因为资源抢占而造成的线程无限等待的问题。
2.线程和锁的关系:
一个线程可以拥有多个锁,一个锁只能被一个线程拥有;
3.死锁代码
synchronized(lockA){
System.out.println("线程1 得到锁A");
synchronized(lockB){
}
}
synchronized(lockB){
System.out.println("线程2 得到锁B");
synchronized(lockA){
}
}
4.排查死锁的工具 jconsole、 jmc、 vm
5.死锁产生的4个条件(必须同时满足)
(1)互斥条件——一个资源只能被一个线程持有,当被一个线程持有之后,就不能被其它线程持有;——不可修改
(2)请求拥有条件——一个线程持有了一个资源之后又试图请求另一个资源;——可以修改
(3)不可剥夺条件——一个资源在被一个线程持有之后,如果这个线程不释放此资源,那么其它线程不能强制获得此资源;——不可修改
(4)环路等待条件——多个线程在获取资源时形成了环形链;——可以修改
6.如何解决死锁问题
从请求拥有条件和环形等待条件入手,修改任意一个:
(1)请求拥有条件
(2)环形等待条件——通过控制获取锁的顺序来解决——破坏了环路等待条件;
7.线程通讯机制
线程通讯:一个线程的动作可以让另一个线程感知到就是线程通讯。
sleep()——线程休眠,必须传递一个明确的结束数据
wait()——线程休眠
notify()——唤醒线程
notifyAll()——唤醒全部线程
8.wait() 为什么要加锁?
wait()在使用的时候需要释放锁,在释放锁之前必须要有一把锁,所有要加锁;
wait()为什么要释放锁?
wait() 默认是不传递任何数据值的,当不传递任何值的时候表示永久等待,这样就造成一把锁被一个线程一直持有,为了避免这种问题的方式,所以在使用wait时要释放锁。
9.注意事项
(1)使用wait() 、notify() 、notifyAll()等方法时必须加锁;
(2)加锁对象和wait() 、notify() 、notifyAll()的对象必须保持一致;
(3)一组wait() 、notify() 、notifyAll()必须是同一对象;
(4)notifyAll()只能唤醒当前对象的所有等待线程;
10.Thread.sleep(0)和Object.wait(0)的区别
(1)sleep是Thread的静态方法;wait() 是Object的方法;
(2)sleep(0)——表示立即触发一次CPU资源的抢占;wait(0)——永久的等待下去
(3)都需要处理throws InterruptedException异常;
11.wait和sleep的区别
(1)相同点:(2)不同点:
- 都可以让当前线程休眠;
- 都必须处理Interrupt 异常;
- wait是Object 的方法,而sleep是Thread的静态方法;
- 传参不同:wait可以无参;sleep必须是一个大于等于0的参数;
- wait使用时必须加锁;sleep使用时不用加锁
- wait使用时会释放锁,而sleep不会释放锁;
- wait默认不传参情况会进入WAITING状态;sleep默认不传参情况会进入TIME_WAITING状态;
12.为什么wait放到Object而不是Thread中?
wait操作必须要加锁和释放锁,而锁是属于对象级别,而不是线程级别(线程和锁是一对多的关系,即一个线程可以拥有多把锁),为了灵活起见(一个线程当中会有多把锁),就把wait放入Object。
13. 线程休眠和唤醒对象——LockSupport(wait的升级)
不需要加锁和释放锁,也不需要处理异常;
LockSupport.park()——线程休眠——进入WAITING状态;
LockSupport.unpark(线程名称)——唤醒线程
14.wait和LockSupport的区别
(1)相同点(2)不同点:
- 两者都可以使线程进行休眠;
- 两者都可以传参或者不传参,并且二者线程状态一致;
- wait需要与Synchronized一起使用(必须加锁),而LockSupport不需要加锁;
- wait只能唤醒全部和随机的一个线程;而LockSupport可以唤醒指定的线程;