一、变量的线程安全分析

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

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

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

 局部变量是否线程安全?

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

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

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

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

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

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

java线程对公共变量赋值 java变量线程安全_java线程对公共变量赋值

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成员变量(共享资源)

java线程对公共变量赋值 java变量线程安全_java线程对公共变量赋值_02

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

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 相同 

java线程对公共变量赋值 java变量线程安全_线程安全_03

 二、常见线程安全类

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都执行上述代码,则不是线程安全的,如下图所示:

java线程对公共变量赋值 java变量线程安全_java_04

2.2 不可变类线程安全性

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

三、结束

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