WangJiQing

导航

线程安全分析

线程安全分析

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

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

例如成员变量list,被两个线程访问,并进行读写操作,没有措施导致线程不安全

public class Example {
    static final int THREADNUMBER = 2;//创建线程个数
    static final int LOOPNUMBER = 2000;//循环调用次数,次数多了,问题就能显现出来
    public static void main(String[] args) throws InterruptedException {
        ThreadUnSafe threadUnSafe = new ThreadUnSafe();
        //创建两个线程,调用method1
        for (int i = 0; i < THREADNUMBER; i++) {
            new Thread(() -> {
                threadUnSafe.method1(LOOPNUMBER);
            }).start();
        }
    }
}

class ThreadUnSafe{
    ArrayList<Integer> list = new ArrayList<>();
    public void method1(int loopNumber){
        for (int i = 0; i < loopNumber; i++) {
            //临界区 产生竟态条件
            method2();
            method3();
            //临界区
        }
    }

    public void method2() {
        list.add(2);
    }

    public void method3() {
        list.remove(0);
    }
}

局部变量是否线程安全?

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

举例,局部变量逃离方法的作用范围,容易产生线程不安全

public class Example {
    static final int THREADNUMBER = 2;
    static final int LOOPNUMBER = 2000;
    public static void main(String[] args) throws InterruptedException {
        ThreadUnSafe threadUnSafe = new ThreadUnSafe();
        //调用method1
        new Thread(() -> {
            threadUnSafe.method1(LOOPNUMBER);
        }).start();
    }
}

class ThreadUnSafe{
    public void method1(int loopNumber){
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            //临界区 产生竟态条件
            method2(list);
            method3(list);
            //临界区
        }
    }

    public void method2(ArrayList<Integer> list) {
        list.add(2);
    }

    public void method3(ArrayList<Integer> list) {
        //新开一个线程,访问list对象
        new Thread(() -> {
            list.remove(0);
        }).start();
    }
}

常见线程安全类

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

这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。也可以理解为它们的每个方法是原子的
但注意它们多个方法的组合不是原子的

String如何保证线程安全?原因是每次修改String类型的对象,都会创建一个新的对象,保证了线程安全,但是效率很低

例如Hashtable,两个线程执行以下操作

if(table.get("key") == null){

​ table.put("key", value);

}

得出来的结果不一致

sequenceDiagram participant t1 as 线程1 participant t2 as 线程2 participant table t1 ->> table : get("key")==null t2 ->> table : get("key")==null t1 ->> table : put("key", value) t2 ->> table : put("key", value)
sequenceDiagram participant t1 as 线程1 participant t2 as 线程2 participant table t1 ->> table : get("key")==null t2 ->> table : get("key")==null t2 ->> table : put("key", value) t1 ->> table : put("key", value)
    public static void main(String[] args) throws InterruptedException {
        Hashtable<String, Integer> table = new Hashtable<>();
        Thread t1 = new Thread(() -> {
            if (table.get("key") == null) {
                table.put("key", 1);
            }
        });

        Thread t2 = new Thread(() -> {
            if (table.get("key") == null) {
                table.put("key", 2);
            }
        });

        t1.start();
        t2.start();
        TimeUnit.MILLISECONDS.sleep(300);
        System.out.println("key的值为:" + table.get("key"));
    }

计算key得出结果为1 或 2

posted on 2022-12-25 12:16  如梦幻泡影  阅读(20)  评论(0编辑  收藏  举报