经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
Java并发(十七)----变量的线程安全分析
来源:cnblogs  作者:|旧市拾荒|  时间:2023/12/11 10:23:15  对本文有异议

1、成员变量和静态变量是否线程安全

  • 如果它们没有共享,则线程安全

  • 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况

    • 如果只有读操作,则线程安全

    • 如果有读写操作,则这段代码是临界区,需要考虑线程安全

 

2、局部变量是否线程安全

  • 局部变量是线程安全的

  • 但局部变量引用的对象则未必

    • 如果该对象没有逃离方法的作用访问,它是线程安全的

    • 如果该对象逃离方法的作用范围,需要考虑线程安全

3、局部变量线程安全分析

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

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

  1. public static void test1();
  2.    descriptor: ()V
  3.    flags: ACC_PUBLIC, ACC_STATIC
  4.    Code:
  5.      stack=1, locals=1, args_size=0
  6.         0: bipush        10
  7.         2: istore_0
  8.         3: iinc          0, 1
  9.         6: return
  10.      LineNumberTable:
  11.        line 10: 0
  12.        line 11: 3
  13.        line 12: 6
  14.      LocalVariableTable:
  15.        Start  Length  Slot  Name   Signature
  16.            3       4     0     i   I

如图

局部变量的引用稍有不同,先看一个成员变量的例子

  1. class ThreadUnsafe {
  2.    ArrayList<String> list = new ArrayList<>();
  3.    public void method1(int loopNumber) {
  4.        for (int i = 0; i < loopNumber; i++) {
  5.            // { 临界区, 会产生竞态条件
  6.            method2();
  7.            method3();
  8.            // } 临界区
  9.       }
  10.   }
  11. ?
  12.    private void method2() {
  13.        list.add("1");  // 访问的同一个成员变量list
  14.   }
  15. ?
  16.    private void method3() {
  17.        list.remove(0);// 访问的同一个成员变量list
  18.   }
  19. }

执行

  1. static final int THREAD_NUMBER = 2;
  2. static final int LOOP_NUMBER = 200;
  3. public static void main(String[] args) {
  4.    ThreadUnsafe test = new ThreadUnsafe();
  5.    for (int i = 0; i < THREAD_NUMBER; i++) {
  6.        new Thread(() -> {
  7.            test.method1(LOOP_NUMBER);
  8.       }, "Thread" + i).start();
  9.   }
  10. }

多运行几次就会发现,其中一种情况是,如果线程2 还未 add,线程1 remove 就会报错:

  1. Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
  2. at java.util.ArrayList.rangeCheck(ArrayList.java:657)
  3. at java.util.ArrayList.remove(ArrayList.java:496)
  4. at cn.itcast.n6.ThreadUnsafe.method3(TestThreadSafe.java:35)
  5. at cn.itcast.n6.ThreadUnsafe.method1(TestThreadSafe.java:26)
  6. at cn.itcast.n6.TestThreadSafe.lambda$main$0(TestThreadSafe.java:14)
  7. at java.lang.Thread.run(Thread.java:748)

分析:

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

  • method3 与 method2 分析相同

将 list 修改为局部变量

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

那么就不会有上述问题了

分析:

  • list 是局部变量,每个线程调用时会创建其不同实例,没有共享

  • 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象

  • method3 的参数分析与 method2 相同

方法访问修饰符带来的思考?

如果把 method2 和 method3 的方法修改为 public 会不会带来线程安全问题?

  • 情况1:有其它线程调用 method2 和 method3

  • 情况2:在 情况1 的基础上,为 ThreadSafe 类添加子类,子类覆盖 method2 或 method3 方法,即

  1. class ThreadSafe {
  2.    public final void method1(int loopNumber) {
  3.        ArrayList<String> list = new ArrayList<>();
  4.        for (int i = 0; i < loopNumber; i++) {
  5.            method2(list);
  6.            method3(list);
  7.       }
  8.   }
  9. ?
  10.    private void method2(ArrayList<String> list) {
  11.        list.add("1");
  12.   }
  13. ?
  14.    private void method3(ArrayList<String> list) {
  15.        list.remove(0);
  16.   }
  17. }
  18. ?
  19. class ThreadSafeSubClass extends ThreadSafe{
  20.    @Override
  21.    public void method3(ArrayList<String> list) {
  22.        new Thread(() -> {
  23.            list.remove(0);
  24.       }).start();
  25.   }
  26. }

这样的话就会存在线程安全的问题。因为method3新开了一个线程,造成多个线程访问同一个共享资源,就会存在线程安全的问题。

从这个例子就可以看出 private 或 final 提供【安全】的意义所在。

 

原文链接:https://www.cnblogs.com/xiaoyh/p/17157329.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号