/** * 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指令本身是不 可再分的,它们都具备原子性,是线程安全的,也叫原子操作。但是, 两个或者两个以上的原子操作合在一起进行操作就不再具备原子性了。 比如先读后写,就有可能在读之后,其实这个变量被修改了,出现读和 写数据不一致的情况