线程安全之a++问题以及打印产生问题的数字
- a++导致线程安全问题
1. a++多线程下数据丢失问题
案例:定义一个全局变量count,2个线程交替执行1万次count++,查看count最终结果。
public class MultiThreadsError implements Runnable {
static int count = 0;
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
MultiThreadsError multiThreadsError = new MultiThreadsError();
Thread thread1 = new Thread(multiThreadsError);
Thread thread2 = new Thread(multiThreadsError);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(count);
}
}
程序结果:count不等于20000。
问题分析
count++ 是一个复合操作:读取 - 执行 - 写入,并且当前数据状态以来上一个数据状态
线程一读取count = 0,然后执行到第二步,count + 1,此时,操作系统将线程时间片交到线程二手上,线程二执行第一步读取count = 0;
当线程一和线程二都执行完成。count 结果为1
拓展
打印出count重复执行那几次?
上代码:
public class MultiThreadsError implements Runnable {
static int count = 0;
static boolean[] arr = new boolean[100000000];
static AtomicInteger counter = new AtomicInteger();
static AtomicInteger countFail = new AtomicInteger();
CyclicBarrier o1 = new CyclicBarrier(2);
CyclicBarrier o2 = new CyclicBarrier(2);
@Override
public void run() {
arr[0] = true;
for (int i = 0; i < 100000; i++) {
try {
o2.reset();
// 等待2个线程一起到达,再一起出发
o1.await();
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
count++;
try {
// 等待2个线程一起到达,再一起出发
o1.reset();
o2.await();
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
counter.incrementAndGet();
synchronized (this) {
// 先判断后执行,会产生线程竞争,导致数据错误。
if (arr[count] && arr[count - 1]) {
System.out.println("fail number " + count);
countFail.incrementAndGet();
} else {
arr[count] = true;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MultiThreadsError multiThreadsError = new MultiThreadsError();
Thread thread1 = new Thread(multiThreadsError);
Thread thread2 = new Thread(multiThreadsError);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(count);
System.out.println("执行次数:" + counter.get());
System.out.println("错误次数:" + countFail.get());
}
}
结果:
代码分析:
- 使用arr数组来标记count的状态,如果2个线程产生竞争,那么假设2个线程得到的值都是1,那么执行if(arr[count])的时候就可以打印出产生竞争的那个数字。
- 定义了2个同步器o1和o2,环绕在count++执行前后,消除count++产生竞争对后续的代码的影响。假设如果不用同步器,那么if判断为true,但是打印的数字不一定是产生竞争的数字。
- 使用synchronized将判断语句锁起来,消除先判断后执行产生的线程问题。