什么叫线程安全? 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 降低同步成本

results matching ""

    No results matching ""