併發的調試較單線程調試更復雜,且併發的問題不一定能復現,
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(樂觀讀模式)是一種優化的讀模式。