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?
可能是a线程算出count=1,然后输出count的时候,此时a线程挂起,b线程执行,b线程对count自增,此时a线程再输出的时候,count已经发生了变化,这就导致了1没有被输出 - 为什么两个2?
可能是a,b两个线程都完成了count的计算,然后a线程输出,输出结束后立即被挂起,然后紧接着b线程立即也进行了输出,那么此时a b线程一定是输出了相同的count,就导致了相同值的出现。 - 将循环次数加大到100或者200,就会发现最后输出的count并不会到100或者200
这是由于count++这个操作也不是原子的,也就是说count++并不是一次性完成,而是分为3步骤。第一步,获取count;第二步,给count指向的值加1;第三步,讲计算结果写回count。因此如果三个步骤再混合上多线程执行顺序的错乱因素,就会导致不可预测的问题了。比如a线程获取count为1,此时a线程立马被挂起,b线程获取count也为1,然后a,b线程各自去执行,最后写回count都是2,这就导致了count被少加一次。
线程安全性
其实我们看到线程安全性的定义的关键点在于正确性,即在多线程的环境下,无论运行时候环境采用如何的调度方式,系统或者类或者方法总能表现出预期的相符的行为,那么就是线程安全的。
总结
- 在多线程环境下,之所以会出现并发的线程安全性问题,是由于多个线程去操纵一个共享的变量或者一组变量,而且变量的操作过程不是原子的,那么线程的执行顺序就会干扰到变量。
- 为了保证线程安全性,解决方法:
- 多个线程访问一个不可变量
- 变量不可以被多线程共享
- 线程做同步处理(原子性处理)
参考内容
- 书籍《Java并发编程实战》