首页 > 编程知识 正文

atomic,proton和atom有什么不同

时间:2023-05-04 12:48:24 阅读:24919 作者:138

摘要原子长是作者Doug Lea在JDK 1.5版中发布到java.util.concurrent.atomic订购的类。

LongAdder是dsdgk(Douglea的中文名称)在java8上发布的类。

为什么有自动长整型却需要长整型Adder? 现在,我们必须分析原子长的缺点。

首先,让我们看看AtomicLong.incrementAndGet ()方法的源代码

/* * * atomicallyincrementsbyonethecurrentvalue.* * @ returntheupdatedvalue */publicfinallongincrementandget () return }

图: Unsafe类的getAndAddLong方法

显而易见,原子长的原子性自增操作是通过CAS实现的。

如果多线程竞争不激烈,这样做比较合适。 但是,如果线程竞争激烈,大量线程会绕着原地旋转,试图修改值,但会发现值已被修改,然后继续旋转。 这样浪费了大量的CPU资源。

此外,由于AtomicLong的成员变量value由volatile关键字限定,因此线程必须在修改关键资源后更新到其他线程,这也很麻烦。

画画理解:

图: volatile刷新共享内存。

LongAdder也有volatile限定的base值,但在竞争激烈的情况下,多个线程采用分段的思路,而不是自旋来修改该值。 在竞争激烈的情况下,每个线程都分布并累积在具有与自己对应的Cell[]数组的数组对象元素中,大家不会共享一个。

这样,可以根据不同的Cell修改不同的线程,减少了对临界资源的竞争。 本质上,是在空间里改变时间。

用半天的时间比较分析了LongAdder和AtomicLong的性能,没有证据,还是不能接受。 只有证明,才能口服。

然后,创建容量为1,000的固定线程池,并提交线程,其容量是线程池容量的100倍。 在每个线程上,对关键资源执行一项操作。 完成所有线程的执行后,对执行时间进行计数并关闭线程池。 临界资源分别用原子长和长Adder表示,比较两者的差异。

首先尝试每个线程100次一次操作。 最后的累计结果如下。

线程数100=1,000100100=10,000,000

AtomicLongDemo.java

import java.text.NumberFormat; import java.util.ArrayList; import Java.util.concurrent.execution exception; import Java.util.concurrent.executorservice; import Java.util.concurrent.executors; import Java.util.concurrent.future; import Java.util.concurrent.atomic.atomic long; /** * pre *计划的目的:在*16个线程上使用AtomicLong,表明AtomicInteger、AtomicLong是高并发的,性能较差。 *每次更改值时,这些flush和refresh操作都会刷新为主内存,并在竞争激烈时占用大量资源。 CAS也是*/pre * created at 2020/8/1106336011 * @ author lerry */publicclassatomiclongdemo {/* *线程池中的线程数*/fff args ) throwsinterruptedexception (长开始=system.current time millis ); atomiclongcounter=newatomiclong (0; executorserviceservice=executors.newfixedthreadpool (pool _ size; arraylistfuturefutures=new ArrayList (pool _ size; for(intI=0; i POOL_SIZE * 100; I ) { futures.add (服务. submit ) newtask(counter ) }; //等待所有线程执行完for(futurefuture:futures ) (try ) future.get ); } catch (执行任务) {e.printStackTrace ); }}NumberFormat numb

erFormat = NumberFormat.getInstance();System.out.printf("统计结果为:[%s]n", numberFormat.format(counter.get()));System.out.printf("耗时:[%d]毫秒", (System.currentTimeMillis() - start));// 关闭线程池service.shutdown();}/** * 有一个 AtomicLong 成员变量,每次执行N次+1操作 */static class Task implements Runnable {private final AtomicLong counter;public Task(AtomicLong counter) {this.counter = counter;}/** * 每个线程执行N次+1操作 */@Overridepublic void run() {for (int i = 0; i < 100; i++) {counter.incrementAndGet();}}// end run}// end class}

LongAdderDemo.java

import java.text.NumberFormat;import java.util.ArrayList;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;import java.util.concurrent.atomic.LongAdder;/** * <pre> * 程序目的:和 AtomicLong 进行性能对比 * </pre> * created at 2020/8/11 06:25 * @author lerry */public class LongAdderDemo {/** * 线程池内线程数 */final static int POOL_SIZE = 1000;public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();LongAdder counter = new LongAdder();ExecutorService service = Executors.newFixedThreadPool(POOL_SIZE);ArrayList<Future> futures = new ArrayList<>(POOL_SIZE);for (int i = 0; i < POOL_SIZE * 100; i++) {futures.add(service.submit(new LongAdderDemo.Task(counter)));}// 等待所有线程执行完for (Future future : futures) {try {future.get();}catch (ExecutionException e) {e.printStackTrace();}}NumberFormat numberFormat = NumberFormat.getInstance();System.out.printf("统计结果为:[%s]n", numberFormat.format(counter.sum()));System.out.printf("耗时:[%d]毫秒", (System.currentTimeMillis() - start));// 关闭线程池service.shutdown();}/** * 有一个 LongAdder 成员变量,每次执行N次+1操作 */static class Task implements Runnable {private final LongAdder counter;public Task(LongAdder counter) {this.counter = counter;}/** * 每个线程执行N次+1操作 */@Overridepublic void run() {for (int i = 0; i < 100; i++) {counter.increment();}}// end run}// end class}

备注:AtomicLong的运行结果截图和LongAdder的运行结果截图放在了一起,AtomicLong的在上、LongAdder的在下。

每个线程进行100次累加的运行结果

图:100次累加的执行结果
可以看到,AtomicLong耗时516毫秒,LongAdder耗时438毫秒,
516➗438≈1.18,前者耗时是后者的一倍多一点。区别好像不是很大。

进行1,000次累加呢?

图:1000次累加的执行结果
可以看到,AtomicLong耗时3034毫秒,LongAdder耗时575毫秒,
3034➗575≈5.28,前者耗时是后者的5倍多。区别开始变得明显。

进行10,000次累加呢?

图:10,000次累加的执行结果
可以看到,AtomicLong耗时30868毫秒,LongAdder耗时2167毫秒,
30868➗2167≈14.24,前者耗时是后者的14倍多。差距变得更大了。

进行50,000次累加呢?

图:50,000次累加的执行结果
可以看到,AtomicLong耗时148375毫秒,LongAdder耗时9754毫秒,
148375➗9754≈15.21,前者耗时是后者的15倍多。差距进一步扩大。

结论

在每个线程执行的累加数量变多时,LongAdder比AtomicLong性能优势越发明显。
LongAdder由于采用了分段理念,降低了线程间的竞争冲突,而AtomicLong却因多个线程并行竞争同一个value值,从而影响了性能。

在低竞争的情况下,AtomicLong 和 LongAdder 这两个类具有相似的特征,吞吐量也是相似的,因为竞争不高。但是在竞争激烈的情况下,LongAdder 的预期吞吐量要高得多,经过试验,LongAdder 的吞吐量大约是 AtomicLong 的十倍,不过凡事总要付出代价,LongAdder 在保证高效的同时,也需要消耗更多的空间。

参考资料

从 LongAdder 中窥见并发组件的设计思路 | 犀利豆的博客

环境说明 java -version java version "1.8.0_251"Java(TM) SE Runtime Environment (build 1.8.0_251-b08)Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode) OS:macOS High Sierra 10.13.4

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。