并发的调试较单线程调试更复杂,且并发的问题不一定能复现,
1、介绍eclipse的条件断点
在加断点的地方,右键选择条件断点:
设置进入断点的条件:
案例:ArrayList(线程不安全)并发添加元素的越界的调试分析:
设置条件断点:
线程1越界后,抛出异常,终止运行,线程0继续运行:
2、使用jstack -l pid:分析线程锁情况
如,可以看到java的死锁的情况。死锁介绍。
3、java8对并发的新支持
1、LongAdder
先说下AtomicLong。在32位操作系统中,64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。而使用AtomicLong能让long的操作保持原子型。实现方式是内部有个volatile value 变量,当多线程并发自增,自减时,均通过自旋的cas 指令从机器指令级别操作保证并发的原子性。AtomicLong中有个内部变量value保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。
LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。有点像锁优化中减小锁的粒度,空间换时间,如jdk7中的ConcurrentHashMap中的“分段锁”类似思想。
LongAdder来说,内部有一个base
变量,一个Cell[]
数组。base
变量:非竞态条件下,直接累加到该变量上,和AtomicLong类似,Cell[]
数组:竞态条件下,累加个各个线程自己的槽Cell[i]
中值; 即:value=base+∑i=0nCell[i]
可以参考:https://segmentfault.com/a/1190000015865714
2、CompletableFuture
先说下Future和Callable实现了用于阻塞式获取结果,Future.get()介绍是等到有结果返回,是阻塞的。
方法:String java.util.concurrent.Future.get() throws InterruptedException, ExecutionException
注释:Waits if necessary for the computation to complete, and then retrieves its result.
CompletableFuture是当任务完成后得到通知,自动调用一些回调方法,执行其他的操作,避免了任务的阻塞,是Future的增强。使用介绍可以参考:https://www.jianshu.com/p/6bac52527ca4
3、StampedLock
ReentrantReadWriteLock使得多个读线程同时持有读锁(只要写锁未被占用),而写锁是独占的。
但是,在读线程非常多,写线程很少的情况下,很容易产生“饥饿”问题,
StampedLock是读写锁的改进,读不阻塞写,Optimistic reading(乐观读模式)是一种优化的读模式。