本文共 1757 字,大约阅读时间需要 5 分钟。
什么是线程安全性
当多个线程访问某个类时,如果不管线程调用的方式或执行顺序,并且主调代码中不需要额外的同步或协同,类能够正确运行,那么这个类就是线程安全的。无状态对象天然就是线程安全的,例如Servlet。
原子性
线程安全的核心需求之一是原子性。
竞争条件
竞争条件是由于不恰当的执行顺序导致不正确结果的情形。 例如,“先检查后执行”的操作,比如延迟初始化: ```javaif(instance == null) { instance = new SomeObject();}```此外,“读取-修改-写入”操作可能导致竞争条件,因为结果依赖于状态,例如递增运算: ```javalong count = 0;count++;```
复合操作
复合操作是指一组互为原子操作的操作集合。为了确保线程安全性,需要将所有涉及同一状态的操作进行原子处理。例如,使用`AtomicLong`来保证递增运算的原子性: ```javaAtomicLong count = new AtomicLong(0);count.incrementAndGet();```
加锁机制
在Java中,通过加锁的方式来维护线程安全性。内置锁是实现这一目的的核心工具。
内置锁
通过`syncronized`关键字修饰的方法或代码块实现原子操作。例如,最简单的`syncronized`方法: ```javapublic synchronized void doSomeWork() { // ...}``` 其中,`syncronized`表示方法内部的代码块是原子操作,整个方法会自动协调线程进入该代码块。
可重入锁
内置锁是可重入的,这意味着同一线程可以多次获得同一锁。这种粒度的锁设计使并发编程更加简便。例如: ```javapublic class LoggingWidget extends Widget { public synchronized void doSomething() { super.doSomething(); // 可重入防止死锁 }}```
用锁保护可变的状态
对于共享多个线程访问的可变状态变量,需要在所有访问代码块中都持有同一个锁,以确保数据的一致性。例如:
活跃性与性能考量
粗粒度的锁会导致活跃性问题,但细化锁的使用可以平衡安全性与性能。例如,在改进后的 Servlet 规划中移除不必要的同步,仅对关键状态进行保护:
public class CachedFactorizer implements Servlet { @GuardedBy("this") private BigInteger lastNumber; @GuardedBy("this") private BigInteger[] lastFactors; public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = null; synchronized (this) { if (i.equals(lastNumber)) { factors = lastFactors.clone(); } } if (factors == null) { synchronized (this) { lastNumber = i; lastFactors = factors.clone(); } } encodeIntoResponse(resp, factors); }}
通过这种方式,可以最大限度地减少锁等时间,同时仍然保证线程安全性。这是优化并发 Servlet 编程中的常见方法。
转载地址:http://waisz.baihongyu.com/