目录
1. 线程安全性
1.1 什么是线程安全性
我们所写的代码在并发情况下使用 时,总是能表现出正确的行为;反之,未实现线程安全的代码,表现的行为是不 可预知的,有可能正确,而绝大多数的情况下是错误的。
1.2 如何实现线程安全性
如果要实现线程安全性,就要保证我们的类是线程安全的。在《Java 并发编程实战》中,定义“类是线程安全的”如下:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在调用代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
1.2.1 线程封闭
就是把对象封装到一个线程里,只有这一个线程能看到此对象。那么这个对 象就算不是线程安全的也不会出现任何安全问题。
1.2.2 栈封闭
栈封闭是我们编程当中遇到的最多的线程封闭。什么是栈封闭呢?简单的说 就是局部变量。多个线程访问一个方法,此方法中的局部变量都会被拷贝一份到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所 以能用局部变量就别用全局的变量,全局变量容易引起并发问题。
1.2.3 TheadLocal
ThreadLocal 是实现线程封闭的最好方法。
ThreadLocal
内部维护了一个
Map
, Map 的
key
是每个线程的名称,而
Map
的值就是我们要封闭的对象。每个线程 中的对象都对应着 Map
中一个值,也就是
ThreadLocal
利用
Map
实现了对象的 线程封闭。
![](https://img-blog.csdnimg.cn/377e9eea5ec141ecaa3d77be2458b07e.png)
1.2.4 无状态的类
没有任何成员变量的类,就叫无状态的类,这种类一定是线程安全的。
1.2.5 让类不可变
让状态不可变,加 final
关键字,对于一个类,所有的成员变量应该是私有 的,同样的只要有可能,所有的成员变量应该加上 final
关键字,但是加上
final
,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能 保证整个类是不可变的。
注意:一旦类的成员变量中有对象,上述的 final
关键字保证不可变 并不能保证类的安全性,为何?因为在多线程下,虽然对象的引用不可变,但是 对象在堆上的实例是有可能被多个线程同时修改的,没有正确处理的情况下,对象实例在堆中的数据是不可预知的。
1.2.6 加锁和 CAS
我们最常使用的保证线程安全的手段,使用 synchronized
关键字,使用显式 锁,使用各种原子变量,修改数据时使用 CAS
机制等等。
2. 死锁
2.1 什么是死锁
是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信 而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
![](https://img-blog.csdnimg.cn/655237d134304c74a36322422d1dd90a.png)
可以总结为:1、死锁是必然发生在多操作者(M>=2 个)争夺多个资源(N>=2 个,且 N<=M)才会发生这种情况。很明显,单线程自然不会有死锁。2、争夺资源的顺序不对,如果争夺资源的顺序是一样的,也不会产生死锁;3、争夺者对拿到的资源不放手。
2.2 如何解决死锁
第一步 定位:
通过 jps
查询应用的 id,再通过
jstack id
查看应用的锁的持有情况。
第二步 修正:
关键是保证拿锁的顺序一致
两种解决方式
1、内部通过顺序比较,确定拿锁的顺序;
2、采用尝试拿锁的机制。
3. 活锁
两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一 个线程总是拿到同一把锁,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。死锁时程序卡顿,时block状态,活锁是runable状态。
![](https://img-blog.csdnimg.cn/23d63af0b9424bd6ae83585ca0ecb02a.png)
解决办法:每个线程休眠随机数,错开拿锁的时间。
4. 线程饥饿
低优先级的线程,总是拿不到执行时间