类的线程安全定义
如果多线程下使用这个类,不过多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的。
类的线程安全表现为:
- 操作的原子性
- 内存的可见性
不做正确的同步,在多个线程之间共享状态的时候,就会出现线程不安全。
怎么才能做到类的线程安全?
栈封闭
所有的变量都是在方法内部声明的,这些变量都处于栈封闭状态。
无状态
没有任何成员变量的类,就叫无状态的类
让类不可变
让状态不可变,两种方式:
1,加final关键字,对于一个类,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上final关键字,但是加上final,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能保证整个类是不可变的。
2、根本就不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值
volatile
保证类的可见性,最适合一个线程写,多个线程读的情景,
加锁和CAS
安全的发布(返回)
类中持有的成员变量,特别是对象的引用,如果这个成员对象不是线程安全的,通过get等方法发布出去,会造成这个成员对象本身持有的数据在多线程下不正确的修改,从而造成整个类线程不安全的问题。
TheadLocal
synchronize (Spring加载一次bean时用synchronize)
HashMap 空间换时间
ConcurrentHashMap 并发
Volatile
Servlet
不是线程安全的类,为什么我们平时没感觉到,:1、Tomcat处理;2、在需求上,很少有共享的需求,第二,接收到了请求,返回应答的时候,都是由一个线程来负责的。
死锁
两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。这些一直在互相等待的进程称为死锁进程。
资源一定是多于1个,同时小于等于竞争的线程数,资源只有一个,只会产生激烈的竞争。
死锁的根本成因:获取锁的顺序不一致导致。
简单的
怀疑发送死锁:
通过jps 查询应用的 id,
再通过jstack id 查看应用的锁的持有情况
解决办法:保证加锁的顺序性
动态的
动态顺序死锁,在实现时按照某种顺序加锁了,但是因为外部调用的问题,导致无法保证加锁顺序而产生的。
解决:
- 通过内在排序,保证加锁的顺序性
- 通过尝试拿锁,也可以。
其他安全问题
活锁
尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生拿锁,释放锁的过程。
解决办法:每个线程休眠随机数,错开拿锁的时间。
线程饥饿
在CPU繁忙的情况下,优先级低的线程得到执行的机会很小,就可能发生线程“饥饿”;持有锁的线程,如果执行的时间过长,也可能导致“饥饿”问题。
解决“饥饿”问题的方案很简单,有三种方案:一是保证资源充足,二是公平地分配资源,三就是避免持有锁的线程长时间执行。这三个方案中,方案一和方案三的适用场景比较有限,因为很多场景下,资源的稀缺性是没办法解决的,持有锁的线程执行的时间也很难缩短。倒是方案二的适用场景相对来说更多一些。