1、常見線程安全類
-
String
-
Integer
-
StringBuffer
-
Random
-
Vector
-
Hashtable
-
java.util.concurrent (JUC)包下的類
這裏說它們是線程安全的是指,多個線程調用它們同一個實例的某個方法時,是線程安全的。
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);
}
結論是線程不安全
1.2 不可變類線程安全性
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);
}
}
2、線程安全實例分析
例1:
public class MyServlet extends HttpServlet {
// 是否安全? 不安全
Map<String,Object> map = new HashMap<>();
// 是否安全? 安全
String S1 = "...";
// 是否安全? 安全
final String S2 = "...";
// 是否安全? 不安全
Date D1 = new Date();
// 是否安全? 不安全 原因屬於可變類型
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 {
// 是否安全? 不安全 修改爲環繞通知即可解決
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 = ?";
// 是否安全 安全
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() {
// 是否安全 不安全 foo暴露出
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();
}
}