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 线程安全的集合类 -- 线程安全的集合类概述

JUC并发编程 -- 变量的线程安全问题(成员变量 & 静态变量 & 局部变量 & 开闭原则 & 理解JDK 中 String 类的实现)

尚硅谷JUC高并发编程学习笔记线程通信与集合线程安全

尚硅谷JUC高并发编程学习笔记线程通信与集合线程安全