开发过程中为了提高性能使用了多线程,并用CountDownLatch计数器确保任务全部完成后返回结果。但是结果仍然产生了偏差,51879条记录只导出51678条,数据丢了! 使用了volatile修饰变量 不可行,最终使用锁在exportList.add(model)加锁。
java内存模型的抽象结构 java线程之间的通信由java内存模型控制,也就是JMM。JMM决定一个线程对共享变量的写入何时对另一个线程可见。线程之间的共享变量在主内存中,每个线程都要一个私有的本地内存。本地内存存储了该线程以读/写共享变量的副本。而上图中的共享变量就是exportList。
内存模型
volatile内存语义
1.可见:对volatile变量的读,能看到任何线程对volatile变量最后的写入。
2.原子性:对任何单个volatile变量的读/写具有原子性,但volatile++的复合操作不具有原子性。
因此用volatile修饰exportList保证了可见性但是不具有原子性。也就是说读的时候变量是最新的,但是写的时候数据覆盖/被覆盖另一个线程的数据。编译器和处理器常常会对指令重新排序。
锁的内存语义
1.线程获取锁时,JMM会把该线程的本地内存置为无效。从而是得被监视器保护的临界区代码必须从祝内存中读取共享变量。当线程释放时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
2.锁的释放和volatile写有相同的内存予以;锁的获取和volatile读有相同的内存语义。
那为什么上图中的变量exportList。t用volatile修饰后数据仍然有误呢。exportList.add()是多步操作,加锁后可以确保多个线程间的该操作符合happens-before原则。即线程A先获取锁之后变量的读取+exportList.add()+刷新变量,必须在B线程的一系列操作之前完成。保证了线程的安全性