请先看
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
由于变成了方法体的局部变量,所以是安全的。MyServlet
和 UserServiceImpl
,虽然有暴漏出来的局部变量,但是,其没有被改变,所以,也是安全的。
当然,对于 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 的行为不确定,所以称之为外星方法。