Java并发基础:变量的线程安全

一、变量的线程安全分析

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

  • 如果它们没有共享,则线程安全
  • 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况:

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

 局部变量是否线程安全?

  • 局部变量是线程安全的
  • 但局部变量引用的对象则未必

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

1.1 局部变量线程安全分析:

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

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

如下图所示,线程0和线程1都调用了test1()方法,他们都会创建一个test1的栈帧,这两个存在于各自线程内部的栈帧互不影响,局部变量i也是栈帧中私有的内存变量值,不会互相影响。

1.2 局部变量引用

 先看一个成员变量的例子: 

public class App 
{
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main( String[] args ) throws InterruptedException {
        ThreadUnsafe test = new ThreadUnsafe();
        for(int i=0;i<THREAD_NUMBER;i++){
            //创建两个线程,对test对象执行相应的操作
            new Thread(()->{
                test.method1(LOOP_NUMBER);
            },"Thread"+(i+1)).start();
        }
    }
}
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);//从集合中移除元素
    }
}

        以上代码创建两个线程,同时对ThreadUnsafe对象中的ArrayList<String>集合执行添加以及删除元素操作。该代码可能会由于线程2还未add元素,线程1remove元素而报错

分析:

    多线程对共享资源有修改操作,产生临界区代码,会有线程安全的问题。

  • 无论哪个线程中的method2()和method3()方法引用的都是同一个对象中的list成员变量(共享资源)

与成员变量对比的局部变量示例(局部变量没有暴漏给外部):

public class App 
{
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main( String[] args ) throws InterruptedException {
        ThreadSafe test = new ThreadSafe();
        for(int i=0;i<THREAD_NUMBER;i++){
            //创建两个线程,对test对象执行相应的操作
            new Thread(()->{
                test.method1(LOOP_NUMBER);
            },"Thread"+(i+1)).start();
        }
    }
}
class ThreadSafe{
    public 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 相同 

 二、常见线程安全类

2.1 常见线程安全类:

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

        此处说的线程安全是指,多个线程调用线程安全类的同一个实例的某个方法时,是线程安全的。可以理解为:

  •  他们的每个方法是原子的
  •  但是注意:他们多个方法的组合不是原子的,

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

        上例中,类Hashtable类是线程安全的类,因此其每个方法都是原子的,即table.get()方法和table.put()方法单个看都是线程安全的。 但是,当两个方法组合在一起时,如两个线程1,2都执行上述代码,则不是线程安全的,如下图所示:

2.2 不可变类线程安全性

        String、Integer 等都是不可变类,因为其内部的状态不可以改变(只能读,不能改写),因此它们的方法都是线程安全的。

三、结束

        本文主要学习了变量的线程安全,其主要落脚点还是看是否共享资源并读写
 

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值