011 线程安全性问题

时间:2021-10-20 13:10:26

一 . 概述

在之前,我们说到线程安全性问题是我们在并发设计首要考虑的问题.

  那么到底什么是并发问题呢?

  看下面的例子(经典的例子):      

public class Problem {
    
    private int count = 0;
    
    public static void main(String[] args) throws Exception {
        Problem demo = new Problem();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int x =0;x<10000;x++) {
                    demo.add();
                }
            }
            
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int x =0;x<10000;x++) {
                    demo.add();
                }
            }
            
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("获取最终的结果count : " + demo.count);
        
    }
    private void add() {
        count++;
    }
}

上面的代码描述了两个线程共同将一个count自增,每人1000次,但是最后运行的结果让我们大失所望,几乎每次都不是2000次.


 

二 .问题的分析

  其实最根本的问题就是两个线程获取的count的状态不一致导致的,也就是说count++操作根本就不是原子性的操作,

    这就造成了线程可以获取到一个另外一个线程的中间状态.

  这可能很抽象,简单说,对count的操作应该是顺序的.


 

三 .安全性问题发生的条件

  在上面我们展示了安全性问题,但是安全性问题何时会发生呢?毕竟,如何我们的程序中如果没有安全性问题,我们就减少了并发时所考虑的问题,这可以帮助我们简化问题.

  线程安全性发生的条件:

  [1]多线程并发情况

  [2]竞争相同资源

  [3]对竞争资源进行非原子操作[常见的就是读写不一致]

解释一个上面的条件:

  前面两个不去解释了.就对第三个条件进行解释.

  原子性:

    这个原子性的概念和我们在事务中的概念是一致的,那就是我们的操作应该是一个完整的个体,要么不发生,要么完全发生.在其中,不会被打断.

  拿上面的例子来说,count++这个操作就不是原子性的,因为这个语句在底层会分成多个语句执行,每个语句都是可以被打断的.


四 .问题的解决

  线程安全性问题的解决最常见的方法就是同步,当然同步的方式有很多种,

    比如加锁,synchronized,CAS等方式,但是他们的核心都是一点,保证原子性.