什么叫线程安全? Servlet线程安全吗?
答案
如果一个共享对象(临界资源)被多个线程同时访问,能够保证其功能和状态的正确性、一致性,那么可称其是线程安全的。
在Web容器中 Servlet对象只有一个实例。多次请求(多个线程)会同时使用这同一个Servlet实例,Servlet实例就成为了临界资源。
Servlet中的代码如果编写正确(不包括可变的实例变量或静态变量),那么不会有线程安全问题。但如果给Servlet添加了可变的实例变量或静态变量(不良做法),就必须考虑其线程的安全性。
分析
1. 线程不安全的例子
public class TestConcurrent {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
count++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
count--;
}
});
t1.start();
t2.start();
t1.join(); // 等待t1运行结束(这时t2在同时运行)
t2.join(); // 等待t2运行结束
System.out.println(count);
}
}
正如以上代码所示,一个线程对static变量count做了一千次自增,另一个线程对它做了一千次自减,但运行结果出人意料:有时候是0,有时候是负数,还有时候是正数。
2. 如何设计线程安全的代码
这是一个很大的话题,仅列举一些主要思想:
- 如果多个线程要同时读写一个临界资源对象,对此临界资源要使用
synchronized
加锁 - 当必须使用某个对象作为临界资源时,考虑使用由JDK 提供的线程安全的实现类,例如
ConcurrentHashMap
,CopyOnWriteArrayList
,AtomicInteger
,StringBuffer
等 - 如果临界资源是只读的(不可变对象),那么没有线程安全安全问题
- 方法内的局部非共享变量、对象,都是线程安全的
- 可以使用
ThreadLocal
避免临界资源的产生 - 在某些场合,可以使用
volatile
降低同步成本