• 并发之线程安全


    1 线程安全

    1 成员变量和静态变量

    • 如果它们没有共享,则线程安全
    • 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
      • 如果只有读操作,则线程安全
      • 如果有读写操作,则这段代码是临界区,需要考虑线程安全

    2 局部变量

    • 局部变量是线程安全的
    • 局部变量引用的对象则未必
      • 如果该对象没有逃离方法的作用访问,它是线程安全的
      • 如果该对象逃离方法的作用范围,需要考虑线程安全

    局部变量线程安全分析

    public static void test1() {
     int i = 10;
     i++;
    }
    
    • 1
    • 2
    • 3
    • 4

    每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享.

    成员变量

    class ThreadUnsafe {
     ArrayList<String> list = new ArrayList<>();
     public void method1(int loopNumber) {
     for (int i = 0; i < loopNumber; i++) {
     // { 临界区, 会产生竞态条件
     method2();
     method3();
     // } 临界区
     }
     }
     private void method2() {
     list.add("1");
     }
     private void method3() {
     list.remove(0);
     }
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
     ThreadUnsafe test = new ThreadUnsafe();
     for (int i = 0; i < THREAD_NUMBER; i++) {
     new Thread(() -> {
     test.method1(LOOP_NUMBER);
     }, "Thread" + i).start();
     }
    }
    /*
    运行报错:
    Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    说明:

    • 无论哪个线程中的 method2 引用的都是同一个对象中的 list 成员变量
    • method3 与 method2 运行相同

    解决方法

    将list改为局部变量

    class ThreadSafe {
     public final void method1(int loopNumber) {
     ArrayList<String> list = new ArrayList<>();
     for (int i = 0; i < loopNumber; i++) {
     method2(list);
     method3(list);
     }
     }
     private void method2(ArrayList<String> list) {
     list.add("1");
     }
     private void method3(ArrayList<String> list) {
     list.remove(0);
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    说明:

    • list 是局部变量,每个线程调用时会创建其不同实例,没有共享
    • method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
    • method3 的参数分析与 method2 相同

    在这里插入图片描述

    3 常见线程安全类

    String, Integer, StringBuffer, Random, Vector, Hashtable, java.util.concurrent包下的类

    多个线程调用它们同一个实例的某个方法时,是线程安全的,即

    Hashtable table = new Hashtable();
    new Thread(()->{
     table.put("key", "value1");
    }).start();
    new Thread(()->{
     table.put("key", "value2");
    }).start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    说明:

    • 它们的每个方法是原子的
    • 多个方法的组合不是原子的

    线程安全类方法的组合

    Hashtable table = new Hashtable();
    // 线程1,线程2
    if( table.get("key") == null) {
     table.put("key", value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1

    不可变类线程安全

    String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的. 但其部分方法,如replace可以改变值,怎么处理的?

    下面案列,给该类添加一个方法,可以添加对象的内容信息.

    public class Immutable{
     private int value = 0;
     public Immutable(int value){
     this.value = value;
     }
     public int getValue(){
     return this.value;
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    改造后

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

    说明:

    • 通过重新创建了一个新对象,来改变了不可变类信息

    4 实例说明

    案列1

    // HttpServlet是Web容器访问中是单例, 即多个线程访问同一个对象
    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) {
         // 使用上述变量
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    案列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++;
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    案列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));
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    案列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) {
     // ...
     }
     }
    }
    
    • 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

    案列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();
     }
    }
    
    • 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

    案列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();
     }
    }
    
    • 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

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

    其中 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();
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    总有一天,你一定会很棒
    从GPU的内存访问视角对比NHWC和NCHW
    VUE3实现个人网站模板源码
    分享一些好用的 react 组件库
    解决No CMAKE_CUDA_COMPILER could be found.No CMAKE_CUDA_COMPILER could be found.
    C++线程安全队列
    在 Android 上测试 Kotlin 协程
    Bean的生命周期
    node多版本管理器nvm
    Java 集合常见知识点&amp;面试题总结(上),2022 最新版!
  • 原文地址:https://blog.csdn.net/ABestRookie/article/details/125465020