什么是线程安全

140 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情

线程安全

首先看下 Brian Goetz 的书 《Java Concurrency In Practice》中对线程安全的解释:

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行问题,也不需要进行额外的同步,而调用这个对象的行为都可以获得正确的结果,那这个对象便是线程安全的。

所以,对于线程安全,就是说在多线程情况下,没有加锁限制或者其他限制,然后同时访问某对象都能得到正确的结果。

线程安全问题

  • 多线程同时操作一个变量导致运行结果错误
public class ThreadTest {
    static Integer count =0;
    public static void main(String[] args) throws InterruptedException {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i =0; i<1000;i++){
                count ++;
                //System.out.println(count);
            }
        };

        Thread th1 = new Thread(r);
        th1.start();
        Thread th2 = new Thread(r);
         th2.start();
        th1.join();
        th2.join();
        System.out.printf("获取值%d\n",count);
    }
}
// 输出结果
获取值:1314

最终结果远小于2000,而且每次执行的结果都不一样。主要因为每个线程执行时得到CPU时间片不一致导致,且出现线程暂停执行时会将 CPU 资源让给其他线程,这样就可能产生线程安全问题。

count ++ 这个过程其实是执行了三个操作:

1、count =1

2、count+1

3、count = 2

这样线程如果执行到count+1 时被其他线程得到执行权,就会使得 count 还是=1 然后继续执行,最后切回线程count =2 这样就是产生线程安全问题,导致数据结果粗线问题。

  • ** 发布和初始化导致线程安全问题**

对象创建后并将其发布和初始化时,另一个线程抢占,获取对象的值,就会导致取值为空。所以如果在操作时间点不对的话就可能导致线程安全的问题。

public class ThreadTest {
    private Map<Integer,String> stus;

    public ThreadTest(){
        new Thread(new Runnale(){
            public void run(){
                stus = new HashMap<>();
                stus.put(1,"Tom");
                stus.put(1,"Terry");
                stus.put(1,"Marry");
            }
        }).start()
    }
    public static void main(String[] args) throws InterruptedException {
      ThreadTest initThread= new ThreadTest();
      String name = initThread.getStus().get(1); 
    }
}

线程启动需要一定时间的,但在 main 函数中没有进行等待就直接获取值,这样就会导致获取的值为null 。

  • 程序始终无法获取到最终的结果

前2种线程安全问题是带来数据错误或报错,但是第三种线程安全问题是程序始终得不到运行的最终结果。比如遇到死锁、活锁和饥饿。也成为活跃性问题。 

死锁:两个线程互相等待对方的资源,但又同时不互相让。

活锁:线程之间没有被限制,都正常运行,但一直都不能得到结果。

饥饿:某线程一直都得不到资源,一直无法运行。