常见线程安全类

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类

这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。也可以理解为

Hashtable table = new Hashtable();
new Thread(()->{
 table.put("key", "value1");
}).start();
new Thread(()->{
 table.put("key", "value2");
}).start();
  • 它们的每个方法是原子的
  • 注意它们多个方法的组合不是原子的,见后面分析

线程安全类方法的组合

分析下面代码是否线程安全?

Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {
 table.put("key", value);
}

常见线程安全类_线程安全

由上图可见并不是线程安全的

不可变类线程安全性

String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的

有同学或许有疑问,String 有 replace,substring 等方法【可以】改变值啊,那么这些方法又是如何保证线程安全的呢?

public class Immutable{
 private int value = 0;
 public Immutable(int value){
 this.value = value;
 }
 public int getValue(){
 return this.value;
 }
}

如果想增加一个增加的方法呢?

public class Immutable{
 private int value = 0;
 public Immutable(int value){
 this.value = value;
 }
 public int getValue(){
 return this.value;
 }
 
 public Immutable add(int v){
 return new Immutable(this.value + v);
 } 
}

实例分析

例1:

public class MyServlet extends HttpServlet {
 // 是否安全? HashMap不是线程安全的,hashtable是线程安全的
 Map<String,Object> map = new HashMap<>();
 // 是否安全?是的,string类不可变
 String S1 = "...";
 // 是否安全? 是的,string类不可变
 final String S2 = "...";
 // 是否安全?不是线程安全的
 Date D1 = new Date();
 // 是否安全?也不是线程安全的 final只能保证引用不可变,内容 年月日是可以修改的
 final Date D2 = new Date();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) { // 使用上述变量
 }
}

例2:

public class MyServlet extends HttpServlet {
 // 是否安全?不是线程安全的,多个线程可以共享
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService {
 // 记录调用次数
 private int count = 0;
 
 public void update() {
 // ...
 count++;
 }
}

例3:

@Aspect
@Component
public class MyAspect {
 // 是否安全?不是线程安全的 start是成员变量,可能会出现多个线程共享(可以改成环绕通知,将成员变量改为局部变量)
 private long start = 0L;
 
 @Before("execution(* *(..))")
 public void before() {
 start = System.nanoTime();
 }
 
 @After("execution(* *(..))")
 public void after() {
 long end = System.nanoTime();
 System.out.println("cost time:" + (end-start));
 }
}

例4:

public class MyServlet extends HttpServlet {
 // 是否安全 是线程安全 是私有的成员变量,没有其他地方可以修改
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService {
 // 是否安全 是线程安全的
 private UserDao userDao = new UserDaoImpl();
 
 public void update() {
 userDao.update();
 }
}
public class UserDaoImpl implements UserDao { 
 public void update() {
 String sql = "update user set password = ? where username = ?";
 // 是否安全 是线程安全的 Connection是局部变量
 try (Connection conn = DriverManager.getConnection("","","")){
 // ...
 } catch (Exception e) {
 // ...
 }
 }
}

例5:

public class MyServlet extends HttpServlet {
 // 是否安全 是线程安全 是私有的成员变量,没有其他地方可以修改
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService {
 // 是否安全 是线程安全的 没有成员变量
 private UserDao userDao = new UserDaoImpl();
 
 public void update() {
 userDao.update();
 }
}
public class UserDaoImpl implements UserDao {
 // 是否安全 不是线程安全的 会被多个线程共享
 private Connection conn = null;
 public void update() throws SQLException {
 String sql = "update user set password = ? where username = ?";
 conn = DriverManager.getConnection("","","");
 // ...
 conn.close();
 }
}

例6:

public class MyServlet extends HttpServlet {
 // 是否安全 是线程安全的
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService { 
 public void update() {    
 UserDao userDao = new UserDaoImpl();
 userDao.update();
 }
}
public class UserDaoImpl implements UserDao {
 // 是否安全 是线程安全的
 private Connection = null;
 public void update() throws SQLException {
 String sql = "update user set password = ? where username = ?";
 conn = DriverManager.getConnection("","","");
 // ...
 conn.close();
 }
}

例7:

public abstract class Test {
 
 public void bar() {
 // 是否安全 不是线程安全的
 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 foo(sdf);
      }
 
 public abstract foo(SimpleDateFormat sdf);
 
 
 public static void main(String[] args) {
 new Test().bar();
 }
}

其中 foo 的行为是不确定的,可能导致不安全的发生,被称之为外星方法

public void foo(SimpleDateFormat sdf) {
 String dateStr = "1999-10-11 00:00:00";
 for (int i = 0; i < 20; i++) {
 new Thread(() -> {
 try {
 sdf.parse(dateStr);
 } catch (ParseException e) {
 e.printStackTrace();
 }
 }).start();
 }
}