JUC并发编程 -- 线程安全实例分析
Posted Z && Y
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC并发编程 -- 线程安全实例分析相关的知识,希望对你有一定的参考价值。
1. 线程安全实例分析
1.1 例题1
Servlet是运行在Tomcat环境下的,他只有一个实例,所以Servlet下面的成员变量肯定会被Tomcat下的多个线程共享使用,因此它里面的成员变量都会存在共享问题,下面我们来分析它的成员变量是否是线程安全的。
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) {
// 使用上述变量
}
}
- HashMap不是线程安全的,HashTable是线程安全的;
- String是线程安全的(不可变了)
- final类型的String是安全的。
- Date不是线程安全的。
- final Date也不是线程安全的(虽然加了final关键字,但是属性还是可以修改的。)
1.2 例题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++;
}
}
不是线程安全的,因为Servlet在内存中只有一份,所以userService 也只有一份,属于共享资源,容易导致互斥。count同理
1.3 例题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));
}
}
这是Spring面向切面编程的代码,因为只有一个start,所以会涉及到资源共享,所以是不安全的。可以利用环绕通知来解决问题。
1.4 例题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) {
// ...
}
}
}
这段代码是MVC三层架构的常见运用
- 这里的dao层是线程安全的,因为没有成员变量并且conn属于方法内的局部变量,即使有多个线程访问,也会创建多个conn对象。
- 这里的service层是安全的,因为里面没有涉及到共享资源的读写操作。虽然有成员变量,但是成员变量是私有的,也没有其余的地方可以修改它。
- controller层也是安全的,与service层同理。
1.5 例题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();
}
}
这段代码也是MVC三层架构的常见运用
- 这里的dao层不是线程安全的,conn属于成员变量,并且后面对它进行了除了读以外的操作。
- 这里的service层不是安全的,因为它调用了dao层的update()方法。
- controller层也不是安全的,因为调用了service层的update()方法。
1.6 例题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();
}
}
这段代码也是MVC三层架构的常见运用
- 这里的dao层是线程安全的,conn属于成员变量,并且后面对它进行了除了读以外的操作,但是因为service层每次调用update()方法都会生成一个新的UserDao 对象,所以不会发生互斥的现象。
- 这里的service层是安全的,service层每次调用update()方法都会生成一个新的UserDao 对象,所以不会发生互斥的现象。
- controller层也是安全的,service层是安全的。
- 补充:虽然是线程安全的,但是不推荐这样写。
1.7 例题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 的行为是不确定的,可能导致不安全的发生,被称之为外星方法
上面代码是非线程安全的,因为foo的行为是不确定,有可能子类重写了foo方法,并且子类重写的方法里面新建了一个线程去操作资源(局部变量–暴露引用)。这里不懂的可以参考:
变量的线程安全问题(成员变量 & 静态变量 & 局部变量 & 开闭原则)
例如可以这样重写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();
}
}
以上是关于JUC并发编程 -- 线程安全实例分析的主要内容,如果未能解决你的问题,请参考以下文章
JUC并发编程 -- 常见的线程安全类 & 线程安全类方法的组合 & 不可变类线程安全性
JUC并发编程 共享模式之工具 JUC 线程安全的集合类 -- 线程安全的集合类概述
JUC并发编程 -- 变量的线程安全问题(成员变量 & 静态变量 & 局部变量 & 开闭原则 & 理解JDK 中 String 类的实现)