/**
* CountDownLatch(倒数闩)是一个非常实用的等待多线程并发的工具类。
* 10个线程并行运行,对一个共享数据进行自增运算,每个线程自增运算1000次,具体
* @author ADMIN
*
*/
public class CountDownLatchTest {
final static int max_tread =10;//10个线程并行运行
final static int max_run =1000;//每个线程自增运算1000次
//测试不安全的累加器
public static void main(String[] args) throws InterruptedException {
//倒数闩 需要倒数10次
CountDownLatch latch =new CountDownLatch(max_tread);
NotSafePlus count=new NotSafePlus();
Runnable runn=()->{
System.out.println(Thread.currentThread().getName());
for(int i=0;i<max_run;i++) {
count.selfPlus();
}
latch.countDown();//倒数闩减少一次
};
for(int i=0;i<max_tread;i++) {
new Thread(runn).start();
}
latch.await();//等待倒数闩的次数减少到0,所有线程执行完毕
System.out.println("理论结果为:"+max_tread *max_run);
System.out.println("实际结果为:"+count.getAmount());
System.out.println("差距为:"+(max_tread *max_run -count.getAmount()));
}
}
class NotSafePlus{
private Integer amount=0;
//自增
public void selfPlus() {
amount++;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
}
为什么自增运算符不是线程安全的呢?实际上,一个自增运算符是 一个复合操作,至少包括三个JVM指令:“内存取值”“寄存器增加 1”和“存值到内存”。这三个指令在JVM内部是独立进行的,中间完全可 能会出现多个线程并发进行。
比如在amount=100时,假设有三个线程同一时间读取amount值,读 到的都是100,增加1后结果为101,三个线程都将结果存入amount的内 存,amount的结果是101,而不是103。
“内存取值”“寄存器增加1”和“存值到内存”这三个JVM指令本身是不 可再分的,它们都具备原子性,是线程安全的,也叫原子操作。但是, 两个或者两个以上的原子操作合在一起进行操作就不再具备原子性了。 比如先读后写,就有可能在读之后,其实这个变量被修改了,出现读和 写数据不一致的情况
本文通过一个CountDownLatch的例子展示了在多线程环境下,不安全的自增运算可能导致结果错误。由于自增操作不是原子性的,当多个线程同时访问时,可能会丢失更新,造成最终结果与预期不符。分析了自增操作的非原子性,并讨论了线程安全问题的重要性。

471

被折叠的 条评论
为什么被折叠?



