变量的线程安全问题

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

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

局部变量是否线程安全?

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

局部变量线程安全分析

  • 局部变量
//方法内部的局部变量不会共享、每个线程调用是会有各自的栈帧内存中创建多份。
public void test(){
    int i= 10;
    i++;
}

  • 局部变量的引用
package cn.yds.juc.learning;

import java.util.ArrayList;
import java.util.List;

/**
 * @author yds
 * @Date 2022/9/28 10:37
 * @Description 线程安全问题
 * @Version 1.0.0
 */
public class 线程安全问题 {
    static int loop1  = 2;
    static int loop2  = 200;
    public static void main(String[] args) {
           ThreadUnsafe threadUnsafe = new ThreadUnsafe();
        for (int i = 0; i < loop1; i++) {
            new Thread(()->{
                threadUnsafe.test(loop1);
            },"t"+(i+1)).start();
        }
    }
}

class ThreadUnsafe {
    //list是成员变量
    List<String> list = new ArrayList();

    public void test(int loop){
        for (int i = 0; i < loop; i++) {
            // 临界区,会产生竞态条件
            add();
            remove();
        }
    }

    private void add() {
        list.add("1");
    }

    private void remove() {
        list.remove(0);
    }
}

class ThreadSafe {

     /**
     * final 修饰的原因:不想让子类重写本方法(子类重新不可控),影响父类的功能。
     * 
     */
    public final void test(int loop){
        //list是局部  变量  没有暴露到外面  线程安全
        List<String> list = new ArrayList();
        for (int i = 0; i < loop; i++) {
            add(list);
            remove(list);
        }
    }
   /**
     * 为了保护线程安全,此处add和remove的访问修饰符 要特别注意。
     * public 修饰修饰的方法可被子类重新,子类中的操作是不可控的。假设子类中重新开启线程操作list.也会有线程安全问题
     */
    private void add(List<String> list) {
        list.add("1");
    }

    private void remove(List<String> list) {
        list.remove(0);
    }
}

执行ThreadUnsafeTest方法会出现报错,由于多个线程之间共享局部变量list造成的

Exception in thread "t1" java.lang.ArrayIndexOutOfBoundsException: -1
    at java.util.ArrayList.add(ArrayList.java:463)
    at cn.yds.juc.learning.ThreadUnsafe.add(线程安全问题.java:42)
    at cn.yds.juc.learning.ThreadUnsafe.test(线程安全问题.java:36)
    at cn.yds.juc.learning.线程安全问题.lambda$main$0(线程安全问题.java:19)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "t2" java.lang.ArrayIndexOutOfBoundsException: -1
    at java.util.ArrayList.remove(ArrayList.java:505)
    at cn.yds.juc.learning.ThreadUnsafe.remove(线程安全问题.java:46)
    at cn.yds.juc.learning.ThreadUnsafe.test(线程安全问题.java:37)
    at cn.yds.juc.learning.线程安全问题.lambda$main$0(线程安全问题.java:19)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "t95" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:657)
    at java.util.ArrayList.remove(ArrayList.java:496)
    at cn.yds.juc.learning.ThreadUnsafe.remove(线程安全问题.java:46)
    at cn.yds.juc.learning.ThreadUnsafe.test(线程安全问题.java:37)
    at cn.yds.juc.learning.线程安全问题.lambda$main$0(线程安全问题.java:19)
    at java.lang.Thread.run(Thread.java:748)

 而ThreadSafe类将list 移动到了test()方法内部(局部变量),以传参数的形式传入add()remove()方法中。每个线程调用都会创建不同的实例,不会共享变量。

//为了线程安全,要控制变量或方法的访问权限。比如private(私有、本类中可用)、final修饰,防止外部对象或者子类操作变量。(开闭原则的闭)

 

常见的线程安全类 

  • String
  • Integer
  • StringBuffffer
  • 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的put源码:

public synchronized V put(K key, V value) {
       //…………实现
        return null;
}

方法被synchronize修饰保证(原子性)线程安全。

注意:他们里面的每个方法是原子性的 ,但是组合到一起并不能保证原子性。

 

不可变类线程安全性

String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的.

如String的subtring、replace等方法。看似对字符串做了操作,实则重新创建了新的对象。如: 

  public String substring(int beginIndex, int endIndex) {
       //……
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);//此处new了新对象
    }

 

posted @ 2022-09-28 15:25  iyandongsheng  阅读(153)  评论(0编辑  收藏  举报