Java并发(一)-了解线程安全

线程不安全性

先来举例说明线程不安全是什么情况下发生的:例如一个变量可以被多个线程进行访问,那么在大量线程并发访问这个变量的情况下,线程执行的顺序会给最后的结果带来不可预估的错误。
先定义一个单例类SimpleWorkingHardSingleton:

public class SimpleWorkingHardSingleton {
	private static SimpleWorkingHardSingleton simpleSingleton = new SimpleWorkingHardSingleton();
	
	// 数量
	private int count;
	
	private SimpleWorkingHardSingleton() {
		count = 0;
	}
	
	public static SimpleWorkingHardSingleton getInstance() {
		return simpleSingleton;
	}

	public int getCount() {
		return count;
	}
	
	public void addCount(int increment) {
		this.count += increment;
		System.out.println(this.count);
	}

}

可以看到下面这个单例若在多线程环境下运行,count是被多个线程同时操纵的变量,示例:

for (int i = 0; i < 5; i++) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            SimpleWorkingHardSingleton simpleSingleton = SimpleWorkingHardSingleton.getInstance();
            simpleSingleton.addCount(1);
        }
    }).start();
}

看输出结果(你的执行结果可能和我的不同):

3
2
2
4
5

匪夷所思的结果,想不懂为什么会有两个2出现,1哪儿去了,为什么输出不是 1 2 3 4 5,下面就来解释一下

  1. 为什么没有1?
    可能是a线程算出count=1,然后输出count的时候,此时a线程挂起,b线程执行,b线程对count自增,此时a线程再输出的时候,count已经发生了变化,这就导致了1没有被输出
  2. 为什么两个2?
    可能是a,b两个线程都完成了count的计算,然后a线程输出,输出结束后立即被挂起,然后紧接着b线程立即也进行了输出,那么此时a b线程一定是输出了相同的count,就导致了相同值的出现。
  3. 将循环次数加大到100或者200,就会发现最后输出的count并不会到100或者200
    这是由于count++这个操作也不是原子的,也就是说count++并不是一次性完成,而是分为3步骤。第一步,获取count;第二步,给count指向的值加1;第三步,讲计算结果写回count。因此如果三个步骤再混合上多线程执行顺序的错乱因素,就会导致不可预测的问题了。比如a线程获取count为1,此时a线程立马被挂起,b线程获取count也为1,然后a,b线程各自去执行,最后写回count都是2,这就导致了count被少加一次。

线程安全性

其实我们看到线程安全性的定义的关键点在于正确性,即在多线程的环境下,无论运行时候环境采用如何的调度方式,系统或者类或者方法总能表现出预期的相符的行为,那么就是线程安全的。

总结

  1. 在多线程环境下,之所以会出现并发的线程安全性问题,是由于多个线程去操纵一个共享的变量或者一组变量,而且变量的操作过程不是原子的,那么线程的执行顺序就会干扰到变量。
  2. 为了保证线程安全性,解决方法:
    • 多个线程访问一个不可变量
    • 变量不可以被多线程共享
    • 线程做同步处理(原子性处理)

参考内容

  1. 书籍《Java并发编程实战》
posted @ 2018-08-20 12:32  闫文雄  阅读(147)  评论(0编辑  收藏  举报