我喜欢新鲜玩意儿,而Java 8里面就有
不少。这回我准备介绍一下我的一个最爱——并发计数器。这是一组新的类,用于维护多个线程并发读写的计数器。新的API带来了显著的性能提升,同时还保证了接口的简单易用。
多核时代来临了之后,大家都开始使用并发计数器,我们先来看一下Java迄今为止提供了哪些实现方式,它们的性能和这个新的API相比,又有什么不同。
脏计数器——选择这种方式意味着多线程直接并发读写一个普通对象或者静态字段。不幸的是,这么做是行不通的。有两个原因,一个是在Java里, A+= B操作不是原子的。如果你打开编译后的字节码看一下,你会发现至少有四条指令——第一条是从堆里将字段值加载到线程栈里,第二条是加载要增加的值,第三条指令将它们进行相加,第四条则将结果写回到字段中。
如果多个线程同时在同一个内存位置进行这个操作,你的一个写操作很有可能就丢掉了,因为另一个线程可能会覆盖了它的值。还有一个很恶心的事就是这个值的可见性。下面还会详细介绍到。
新手非常容易犯这样的错误,而这样的问题却很难发现。如果你发现团队中有人这么做,最好能帮我个小忙。在你的数据库里面搜一下我的名字“Tal Weiss"。如果你发现我在里面——赶紧把我的记录删掉。这样我会感觉舒服点。
synchronzied——这是最基础的同步操作了,只要你在读写值,它就会阻塞住其它的所有线程。这种方式的确行得通,不过肯定的是,你的程序运行起来会像
DMV排的长队那样。
读写锁(RWLock)——这个和基础的Java锁相比就巧妙了些,它可以让你区分出那些要修改值因此需要阻塞别人的进程以及那些只是读取值不需要进入临界区的。虽然这个方法有的时候很高效(比如写线程的数量比较少的话),但还是相当无语,因为当你获取写锁的时候还是会阻塞住其它线程的执行。
volatile——这个经常会被误用的关键字会让JIT编译停止在运行时进行机器码的优化工作,因此字段一旦有更新别的线程马上就能看到。
它会使得JIT编译器经常玩的一些把戏比如说调整赋值语句的顺序这些无法进行。JIT编译器有可能会改变字段的赋值顺序。什么,你再说一遍?是的,你听的没错。这个神秘的小把戏使得它可以减小程序访问全局堆的次数,同时它还能保证不会影响到你的程序的执行。这真是有点偷偷摸摸的感觉。
那什么时候应该使用volatile计数器?如果你只有一个线程在更新一个值,而多个线程在读的话,这是个很合适的场景。因为完全没有竞争。
你可能会问为什么都使用它就完了?因为如果有多个线程在更新的话就会有问题了。由于A+=B不是一个原子操作,这么做的话可能会覆盖掉别人写的话。在Java 8以前,这种情况你就只能用AtomicInteger了。
AtomicInteger——这组类使用了处理器的CAS (compare-and-swap)指令来更新计数器的值。听起来不错吧?一半一半吧。由于它直接使用机器指令来设置值,因此对其它线程的影响最小。不好的一面是如果它和别的线程有竞争赋值失败了,它会继续重试。在高并发的条件 下,这就成了一个自旋锁,线程会在一个无限的循环内不断的尝试赋值,直到成功为止。我们可不太想看到这种局面。Java 8来了,还带来了LongAdders。
Java 8 Adders——这是个非常棒的新的API,我对它的仰慕有如滔滔江水连绵不绝。从使用者的角度来说,它很像AtomicInteger。只需要创建一个LongAdder对象,然后使用intValue()以及add()方法来获取和设置它的值。而奇迹就发生在这一切的背后。
如果由于竞争这个类的CAS操作失败了的话,它会要添加的值存到一个线程本地的内部的cell对象里。当intValue()方法调用 的时候,它把这些cell的值加到总和里。这样就减少了CAS重试或者阻塞别的线程的情况。真不错的想法。
说的也差不多了。我们来看看它的真本事。我们做了如下的一个基准测试:把一个计数器设置为0,然后多个线程开始读取并进行自增。当计数器到达10^8的时候停止。我们在一个4核的i7处理器上运行这个测试。
我用了10个线程来运行这个基准测试——读写分别使用5个线程来进行,这样的话会出现严重的竞争条件:
注意:脏读和volatile都有可能产生脏值。
测试的代码在
这里。
结论
并发的Adder类和AtomicInteger相比有60~100%的性能提升。
增加线程不会对结果有太大影响,除非是使用锁的情况。
注意到如果使用synchronized或者读写锁,性能会有很大的损耗——慢了一个数量级!
如果你已经在代码里使用到它了——我会感到非常高兴。
译注:想深入了解LongAdders的工作原理的话,可以读下并发编程网上的
这篇文章。
原创文章转载请注明出处:
http://it.deepinmind.com
英文原文链接
分享到:
相关推荐
- **Lock接口与ReentrantLock** 提供了比`synchronized`更细粒度的锁控制,可以实现公平锁和非公平锁,以及可中断和可重入的特性。 4. **并发设计模式** - **生产者-消费者模式** 使用队列作为缓冲区,一个线程...
3. **读写锁(ReentrantReadWriteLock)**:当读操作远多于写操作时,`java.util.concurrent.locks.ReentrantReadWriteLock`比`synchronized`更高效。读操作可并发执行,而写操作互斥。 4. **信号量(Semaphore)**...
### Java并发编程实践 #### 一、并发编程基础 ##### 1.1 并发与并行的区别 ...通过上述知识点的学习,我们可以更好地理解和掌握Java并发编程的基本原理和技巧,为开发高效稳定的并发应用程序打下坚实的基础。
Java并发工具包中包含了一些同步辅助类,如Semaphore(信号量)、CyclicBarrier(循环屏障)和CountDownLatch(计数器门锁)。它们帮助协调多线程间的协作,控制线程的并发访问数量或等待特定条件。 六、FutureTask...
通过阅读《Java 并发编程实战》,开发者将能够掌握Java并发编程的核心概念和最佳实践,提升在多线程环境下的编程能力,从而写出更高效、更稳定的并发应用程序。无论是初学者还是经验丰富的开发人员,都能从中...
6. **原子变量**:分析AtomicInteger、AtomicLong和AtomicReference等原子变量类,以及它们如何实现无锁编程和高效并发操作。 7. **线程局部变量**:解释ThreadLocal类的工作原理,如何为每个线程提供独立的变量...
这本书涵盖了Java并发编程的核心概念和技术,旨在帮助开发者在实际项目中高效地处理高并发场景。随书附带的代码提供了丰富的示例,以便读者能够更直观地理解并实践这些理论知识。 1. **Java并发基础** - **线程与...
再者,原子操作类(如AtomicInteger、AtomicLong等)也是Java并发编程的重要工具。它们提供了一种无锁编程的方式,通过硬件指令确保某些操作的原子性,避免了线程同步的开销。 并发集合类如ConcurrentHashMap、...
`Lock`接口提供了比`synchronized`关键字更细粒度的锁控制,包括可重入锁、公平锁、非公平锁等。它提供了tryLock()方法,可以在无法立即获得锁时返回,避免了线程被阻塞。 #### 2.1.1. ReentrantLock `...
3. **locks**:此包提供了高级锁定机制,如`ReentrantLock`和`ReadWriteLock`,比内置的`synchronized`关键字更灵活,可以实现更复杂的同步策略。 在学习JUC的过程中,你需要理解以下关键概念: - **线程池**:...
**并发工具类**:Java并发包(java.util.concurrent)提供了丰富的并发工具类,如Semaphore(信号量)、CyclicBarrier(循环栅栏)、CountDownLatch(计数器门锁)、Future和Callable接口等,它们在解决特定并发问题...
- **Lock接口与ReentrantLock**:比`synchronized`更灵活,支持公平锁和非公平锁,提供锁获取和释放的显式控制。 3. **线程通信**: - **wait()、notify()和notifyAll()**:基于对象监视器的通信方式,需在同步块...
Java并发工具包(Java Concurrency Utility,简称JUC)是Java平台中用于高效并发编程的重要模块,它在`java.util.concurrent`包下提供了一系列高级并发工具。这些工具可以帮助开发者更好地管理和控制多线程环境,...
还有`Lock`接口和它的实现,如`ReentrantLock`,提供了比`synchronized`更细粒度的锁控制。 通过研究这个压缩包中的案例源码和讲解,你可以学习如何在实际项目中应用这些并发工具,理解它们的工作原理,并掌握解决...
通过学习《Java并发编程实战》,开发者能够全面掌握Java并发编程的核心知识,从而编写出更高效、更稳定、更易于维护的并发程序。这本书是Java并发编程者的必备参考书,对于提升并发编程能力有着极大的帮助。
在Java中,`concurrent`包提供了丰富的工具类和接口,用于实现安全、高效的并发操作。本示例测试主要探讨了以下几个关键知识点: 1. **线程**:在Java中,`Thread`类是线程的基础,通过继承`Thread`或实现`Runnable...
Java并发编程是Java开发中的重要领域,特别是在大型系统和服务器端应用中,高效地利用多核处理器资源,实现线程安全和程序响应速度的提升,是开发者必须掌握的关键技术。这个压缩包“Java并发编程代码(包含运行依赖...
- **Executor框架**:介绍了ExecutorService、ThreadPoolExecutor和ScheduledExecutorService等,这些是线程池的实现,用于更高效地管理线程。 3. **同步机制** - **锁**:讲述了synchronized关键字的用法,包括...