变量的线程安全分析

举报
yd_249383650 发表于 2023/06/28 15:50:12 2023/06/28
【摘要】 ​ 目录变量的线程安全常见线程安全类变量的线程安全成员变量和静态变量是否线程安全?如果它们没有共享,则线程安全如果它们被共享了,根据它们的状态是否能够改变,又分两种情况如果只有读操作,则线程安全如果有读写操作,则这段代码是临界区,需要考虑线程安全局部变量是否线程安全?局部变量是线程安全的但局部变量引用的对象则未必如果该对象没有逃离方法的作用访问,它是线程安全的如果该对象逃离方法的作用范围,需...

 

目录

变量的线程安全

常见线程安全类



变量的线程安全

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

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

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

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

局部变量是否线程安全?

局部变量是线程安全的

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

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

局部变量线程安全分析

public static void test1() {
 int i = 10;
 i++;
}

每个线程调用 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);
    }
}

执行

其中一种情况是,如果线程2 还未 add,线程1 remove 就会报错:

Exception in thread "Thread0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:659)
	at java.util.ArrayList.remove(ArrayList.java:498)
	at com.cc.db.ThreadUnsafe.method3(Gun.java:35)
	at com.cc.db.ThreadUnsafe.method1(Gun.java:24)
	at com.cc.db.ThreadUnsafe.lambda$main$0(Gun.java:16)
	at java.lang.Thread.run(Thread.java:750)

分析: 无论哪个线程中的 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);
    }
}

那么就不会有上述问题了

分析:

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

 编辑

方法访问修饰符带来的思考,如果把 method2 和 method3 的方法修改为 public 会不会代理线程安全问题?

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

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


常见线程安全类

  • 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();
  • 它们的每个方法是原子的
  • 但注意它们多个方法的组合不是原子的 

线程安全类方法的组合

分析下面代码是否线程安全?

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

编辑

 即线程1判为空以后,并没有立刻put进去数据,而线程2这个时候也进行了判空,马上进行了put数据。线程1这时才put数据

不可变类线程安全性

String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的 有同学或许有疑问,String 有 replace,substring 等方法【可以】改变值啊,那么这些方法又是如何保证线程安 全的呢?

Java中的String类是不可变类,这意味着一旦创建String对象,它的值就无法更改。因此,对于String对象的任何操作都不会影响原始对象,而是创建并返回一个新的String对象。

对于replace和substring等方法,实际上是返回了一个新的字符串对象,而不是在原始的字符串对象上进行修改,所以这样的操作不会引起线程安全问题。

具体来说,在Java中,String类的任何方法都是线程安全的,因为String类对象本身是不可变的,即String类的实例字段都声明为final类型,这些实例字段的值一旦被初始化后便无法更改,同时这些实例字段也没有提供任何可以修改它们的方法。因此,多个线程可以同时访问并共享同一个String对象,而不需要担心线程安全问题。

需要注意的是,如果在多线程环境下使用StringBuilder类或StringBuffer类,则需要采取相应的线程安全措施,因为它们是可变的字符串类,可能会引起线程安全问题。例如,可以使用synchronized关键字对StringBuilder类或StringBuffer类的共享代码块进行同步,或者使用ThreadLocal使得每个线程都拥有自己的StringBuilder或StringBuffer实例。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。