使用 JLM 分析应用程序
JLM 提供了 Java 应用和 JVM 中锁持有时间和沖突统计具体提供以下功能
对沖突的锁进行计数
成功获得锁的次数
递归锁的次数
申请锁的线程被阻塞等待的次数
锁被持有的累计时间对于支持 Tier Spin Locking 的平台 还可以获得以下信息 :
请求线程在内层(spin loop)请求锁的次数
请求线程在外层(thread yield loop)请求锁的次数
使用 rtdriver 工具收集更详细的信息
jlmlitestart仅收集计数器
jlmstart仅收集计数器和持有时间统计
jlmstop停止数据收集
jlmdump打印数据收集并继续收集过程
从锁持有时间中去除垃圾收集(Garbage CollectionGC)的时间
GC 时间从 GC 周期中所有被持有的锁的持有时间中去除
使用 AtomicInteger 进行计数
通常在我们实现多线程使用的计数器或随机数生成器时会使用锁来保护共享变量这样做的弊端是如果锁竞争的太厉害会损害吞吐量因为竞争的同步非常昂贵
volatile 变量虽然可以使用比同步更低的成本存储共享变量但它只可以保证其他线程能够立即看到对 volatile 变量的写入无法保证读 修改 写的原子性因此volatile 变量无法用来实现正确的计数器和随机数生成器
从 JDK 开始ncurrentatomic 包中引入了原子变量包括 AtomicIntegerAtomicLongAtomicBoolean 以及数组 AtomicIntergerArrayAtomicLongArray 原子变量保证了 ++——+== 等操作的原子性利用这些数据结构您可以实现更高效的计数器和随机数生成器
加入轻量级的线程池—— Executor
大多数并发应用程序是以执行任务(task)为基本单位进行管理的通常情况下我们会为每个任务单独创建一个线程来执行这样会带来两个问题一大量的线程(>)会消耗系统资源使线程调度的开销变大引起性能下降二对于生命周期短暂的任务频繁地创建和消亡线程并不是明智的选择因为创建和消亡线程的开销可能会大于使用多线程带来的性能好处
一种更加合理的使用多线程的方法是使用线程池(Thread Pool) ncurrent 提供了一个灵活的线程池实现Executor 框架这个框架可以用于异步任务执行而且支持很多不同类型的任务执行策略它还为任务提交和任务执行之间的解耦提供了标准的方法为使用 Runnable 描述任务提供了通用的方式 Executor 的实现还提供了对生命周期的支持和 hook 函数可以添加如统计收集应用程序管理机制和监视器等扩展
在线程池中执行任务线程可以重用已存在的线程免除创建新的线程这样可以在处理多个任务时减少线程创建消亡的开销同时在任务到达时工作线程通常已经存在用于创建线程的等待时间不会延迟任务的执行因此提高了响应性通过适当的调整线程池的大小在得到足够多的线程以保持处理器忙碌的同时还可以防止过多的线程相互竞争资源导致应用程序在线程管理上耗费过多的资源
Executor 默认提供了一些有用的预设线程池可以通过调用 Executors 的静态工厂方法来创建
newFixedThreadPool
提供一个具有最大线程个数限制的线程池
newCachedThreadPool
提供一个没有最大线程个数限制的线程池
newSingleThreadExecutor
提供一个单线程的线程池
保证任务按照任务队列说规定的顺序(FIFO
LIFO
优先级)执行
newScheduledThreadPool
提供一个具有最大线程个数限制线程池
并支持定时以及周期性的任务执行
使用并发数据结构
Collection 框架曾为 Java 程序员带来了很多方便但在多核时代Collection 框架变得有些不大适应多线程之间的共享数据总是存放在数据结构之中如 MapStackQueueListSet 等 Collection 框架中的这些数据结构在默认情况下并不是多线程安全的也就是说这些数据结构并不能安全地被多个线程同时访问 JDK 通过提供 SynchronizedCollection 为这些类提供一层线程安全的接口它是用 synchronized 关键字实现的相当于为整个数据结构加上一把全局锁保证线程安全
ncurrent 中提供了更加高效 collection如 ConcurrentHashMap/Set ConcurrentLinkedQueue ConcurrentSkipListMap/Set CopyOnWriteArrayList/Set 这些数据结构是为多线程并发访问而设计的使用了细粒度的锁和新的 Lockfree 算法除了在多线程条件下具有更高的性能还提供了如 putifabsent 这样适合并发应用的原子函数