1.线程安全问题
死锁和脏数据就是典型的线程安全问题。
简单来说,线程安全就是: 在多线程环境中,能永远保证程序的正确性
只有存在共享数据时才需要考虑线程安全问题
其中, 方法区和堆就是主要的线程共享区域。那么就是说共享对象只可能是类的属性域或静态域
2.什么才是线程安全
- 线程安全的代码是多个线程同时执行也能工作的代码
- 如果一段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的
线程安全任何合理定义的核心都是在讲线程安全的正确性。因此,在了解线程安全之前,我们首先应该了解这个 “正确性”
正确性意味着类的行为符合规范,当一个类被多个线程进行访问并且正确运行,它就是线程安全的
一个很好的线程安全的例子是没有字段和引用的 Servlet 类,因为没有字段和引用所以是 无状态 的
public class StatelessFactorizer implements Servlet{
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}
特定计算的临时状态被存储在线程堆栈的局部变量表中,只能被当前执行的线程访问。 线程A访问 StatelessFactorizer
不会影响线程B访问同一个 StatelessFactorizer
; 因为两个线程不共享状态,就像是在访问不同的实例。 由于访问无状态的对象时线程的操作不会影响其他线程中操作的正确性,因此无状态对象是线程安全的
多线程假想:众所周知,Servlet是被设计为单实例,在请求进入tomcat后,由Connector建立连接,再讲请求分发给内部线程池中的Processor,
此时Servlet就处于一个多线程环境。即如果存在几个请求同时访问某个servlet,就可能会有几个线程同时访问该servlet对象。
多线程模型下的并发访问策略
1、java监视器模式。 一直使用某一对象的锁来保护某状态。
2、线程安全委托。 将类的线程安全性委托给某个或多个线程安全的状态变量(注意多个时,这些变量必须是彼此独立,且不存在相关联的不变性条件)
3.解决机制
1、加锁:
a、锁能使其保护的代码以串行的形式来访问,当给一个复合操作加锁后,能使其成为原子操作。一种错误的思想是只要对写数据的方法加锁,其实这是错的,对数据进行操作的所有方法都需加锁,不管是读还是写
b、加锁时需要考虑性能问题,不能总是一味地给整个方法加锁synchronized就了事了,应该将方法中不影响共享状态且执行时间比较长的代码分离出去
c、加锁的含义不仅仅局限于互斥,还包括可见性。为了确保所有线程都能看见最新值,读操作和写操作必须使用同样的锁对象
2、不共享状态:
无状态对象: 无状态对象一定是线程安全的,因为不会影响到其他线程
线程关闭: 仅在单线程环境下使用
3、不可变对象:
可以使用final修饰的对象保证线程安全,由于final修饰的引用型变量(除String外)不可变是指引用不可变,但其指向的对象是可变的,所以此类必须安全发布,也即不能对外提供可以修改final对象的接口