一 . 概述
在之前,我们说到线程安全性问题是我们在并发设计首要考虑的问题.
那么到底什么是并发问题呢?
看下面的例子(经典的例子):
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等方式,但是他们的核心都是一点,保证原子性.