0%

java | 线程安全典型分析案例

请先看

spring boot 案例

有一种切面写法,比如,算出一个方法执行多长时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Aspect
@Component
public class MyAspect{
// 是否安全
private long start = 0L;

@Befor("execution(* *(..))")
public void before(){
start = System.nanoTime();
}

@After("execution(* *(..))")
public void afer(){
long end = System.nanoTime();
System.out.println(end - start);
}
}

这是线程不安全的,因为,注解后,对于 spring 来说就是单例模式。单例模式下,就会出现线程不安全的情况。

究其原因是因为,start 不是私有变量,而是可以被共享的。

嵌套调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MyServlet extends HttpServlet{
// 是否安全
private UserService userService = new UserServiceImpl();

public void deGet(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 = "";
conn = DriverManager.getConnection("","","");
//...
conn.close();
}
}

上面的代码并不是线程安全的,以 UserDaoImpl 为例,由于 conn 是暴漏的,所以,当有的线程刚建立连接的时候,另外一个线程可能就直接给 close 了。

可以改成下面这样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MyServlet extends HttpServlet{
// 是否安全
private UserService userService = new UserServiceImpl();

public void deGet(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() throws SQLException{
String sql = "";

try(Connection conn = DriverManager.getConnection("","","")){
//...
}catch(Exception e){
//...
}
}
}

UserDaoImpl 由于变成了方法体的局部变量,所以是安全的。MyServletUserServiceImpl,虽然有暴漏出来的局部变量,但是,其没有被改变,所以,也是安全的。

当然,对于 UserDaoImpl 里面,内部 SQL 的执行,不一定是安全的,因为可能存在 update 某一个数值 +1 的时候,出现错误。

再来看一个代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MyServlet extends HttpServlet{
// 是否安全
private UserService userService = new UserServiceImpl();

public void deGet(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 conn = null;

public void update() throws SQLException{
String sql = "";
conn = DriverManager.getConnection("","","");
//...
conn.close();
}
}

这个时候的 UserDaoImpl 调用也是线程安全的。

外星方法

以抽象类为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 是这个样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
}

其中 sdf.parse(dateStr) 是线程不安全的。由于 foo 的行为不确定,所以称之为外星方法。

请我喝杯咖啡吧~