- 什么是线程安全?
- 造成线程安全的因素?
- 为什么要保证线程安全?
- 怎么保证线程安全?
- 什么时候需要线程安全?
什么是线程安全呢?
线程安全性的定义中,最核心的概念就是正确性,如果对线程安全的定义是模糊的,那么就是因为缺乏对正确性的清晰定义。所谓的正确性就是说:某个类的行为与其规范完全一致。在对正确性给出了较为清晰的定义后,就可以定义线程安全性:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类时线程安全类。
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类就能表现出正确的行为,那么就称这个类是线程安全的。
造成线程安全的因素?
主要有两点:
- 共享资源(Shared)
可变资源(Mutable)
为什么要保证线程安全?
其实就一个根本原因:保证各个线程访问的正确性怎么保证线程安全?
如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么线程就会出现错误。有三种方式可以保证安全性:
1.不在线程之前共享该变量的状态
2.将状态变量修改为不可变的变量
3.在访问状态变量时使用同步
什么时候需要线程安全?
一句话概括:
一个对象是否需要时线程安全的,取决于它是否需要被多个线程访问。
其它相关概念:
原子性
原子性的操作一定是安全的
public class UnsafeCountingFactorizer implements Servlet{
private long count = 0;
public long getCount(){
return this.count;
}
public void service(ServletRequest req, ServletResponse resp){
BigInteger i = extractFromRequest(req);
BigInteger [] factors = factor(i);
++count;
encodeIntoResponse(resp,factors);
}
}
UnsafeCountingFactorizer 是线程非安全的,尽管它在单线程环境中能够正确的执行。++count看上去是一个紧凑的语法,其实这个操作是非原子操作,它包含三个操作步骤:读取-修改-写入,并且其结果依赖于之前的状态。所有在多线程方式访问下和可能会丢失一些更新操作。
资源竞争
既然多个线程访问共享的资源或者可变资源,那么资源只有一个多个线程访问是必须要有竞争
竞态条件
当某个计算的结果的真确性取决于多个线程的交替执行的时序时,那么就发生竞态条件。
通俗点将就是 正确的结果要按照正确的顺序执行
内置锁
Java提供了一种内置锁的机制来支持原子性:同步代码块(Synchronized Block)
synchronzied(loc){}
重入
当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁时可以重入的,因此如果某个线程试图获得由它自己持有的锁,那么这个请求就会成功
代码示例:
public class Widget{
public synchronzied void doSomething(){
//相关业务处理
}
}
public class LoggingWidget extends Widget{
public synchroinzed void doSomething(){
super.doSomething()
}
}
如果内置锁是不可重入的,则上面的代码将发生死锁
注意事项
1.对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁保护的。
2.每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。
3.对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要有同一个锁来保护。
4.当执行时间较长的计算或者可能无法快速完成的操作(例如:网络IO或者控制台IO),一定不要持有锁。