這兩個要搞一個萬級接入量的算法功能壓測程序,查了一下網上的相關資料。測試方案主要參考了下面這篇文章,但是實現和測試方式不同。
reference:http://blog.csdn.net/zhao9tian/article/details/40346899
(之前已經對多線程的相關概念和工程上實際應用需要注意的地方有所了解。但是真正搞這個壓測過程中還是出了很多問題。建議先要搞清并發和并行,內存可見性和線程安全性,synchronized的使用方式和鎖的概念)
要求:模擬200個設備,盡量瞬間并發量達到200。
思路
第一種:線程池模擬200個線程——wait等待線程數達200——notifyAll喚醒所有線程
第二種:線程池模擬200個線程——阻塞線程——達到200條件釋放
比較
兩種方案都可以實現瞬時高并發的模擬,但是建議使用第二種方案。
第一種方案中,壓測過程中,wait狀態下的線程已經釋放對象上的鎖定,喚醒時會極大的消耗CPU資源。壓測程序可能直接導致機器崩潰
第二種方案,由于阻塞過程中,線程不會釋放掉目前持有的對象資源,因此等待條件釋放不會造成資源的過度消耗。
但是應當選擇好阻塞方式,避免線程操作時的死鎖。同時實現線程之間的通信。
wait-notifyAll
代碼較簡單,通過線程池啟動1000個線程,通過synchronized保證線程可見性,和安全性。
當線程數量未達到1000時,wait使線程退出CPU,釋放鎖。
當線程數量達到1000時,notifyAll通知等待集上的線程,喚醒線程。
代碼如下:
/**
* @author:???? irvingyuan
* @since?????? 2017年1月22日 下午4:51:51
* @version:
*/
public class Parallellimit {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
Counts count = new Counts(); ?//共享操作該計數變量,不能使用int或者integer,Java無法對非對象、和包裝類進行加鎖wait
count.num = 0;
for(int i=0;i<10000;i++){ ? ? //啟動線程
MyRunnable runnable = new MyRunnable(count);
pool.execute(runnable);
}
pool.shutdown(); ? ? //關閉線程池,無法加入新線程任務,但不影響現有線程
}
}
public class MyRunnable implements Runnable{
private Counts count ;
/**
* 通過構造方法傳入初值,避免set和get時線程的不安全性
*/
public MyRunnable(Counts count){
this.count = count;
}
public void run() {
try {
/**
* 加鎖,保證線程可見性和安全性
*/
synchronized (count) {
count.num++;
if(count.num<10000){
System.out.println(count.num);
count.wait();//一定要調用count對象的wait,默認對this,無法持有線程的鎖,拋出異常
}
/**
* 達到10000時喚醒所有線程
*/
if(count.num == 10000){
count.notifyAll();
}
System.out.println("并發量 count="+count.num);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
測試結果
并發喚醒1000個線程時,CPU瞬時使用率瞬時增長17%左右。可見CPU負擔很大。
繼續增大線程數,JVM拋OOM異常退出,需要修改啟動參數
block阻塞方式
同步代碼塊持有count的鎖,保證創建出正確的線程數量。判斷不夠并發量時,使用while阻塞線程。
當達到并發量時,阻塞條件失效,線程繼續運行。
代碼如下:
/**
* 阻塞方式創建瞬時高并發
* @author:???? irvingyuan
* @since?????? 2017年1月23日 下午4:45:56
* @version:
*/
public class BlockRunnable implements Runnable{
private Counts count ;
/**
* 通過構造方法傳入初值,避免set和get時線程的不安全性
*/
public BlockRunnable(Counts count){
this.count = count;
}
public void run() {
/**
* this肯定失效,this調用處為runnable對象
* 此時加鎖表示多個線程只能有一個線程在某時刻操作該runnable
* new出來了n個線程,自己調用自己的,this必定失效
* synchronized (this) {
*/
synchronized (count) {
count.num++;
System.out.println("Thread count = "+count.num);
}
/**
* 注意synchronized的粒度
* while放在代碼快中會導致線程一直持有鎖等待,下一個線程無法生成和進行
*/
while(count.num<100);
//并發操作
System.out.println("concurrency count = "+count.num);
}
}
測試效果
100個線程瞬時的CPU使用率居然激增到了100%,和資料說的完全想法,更加損耗系統資源。(是不是因為while?)
//原文使用sleep,個人認為時間不好掌握,用while直接長時間做條件阻塞
CountDownLatch
Java提供的實現阻塞和釋放線程的類,嘗試是否符合推薦的規律。
其中主要包含三個方法
countDownLatch(100)?????類通過構造方法傳入計數數量。
countDown()?????方法會減少一個計數值
await()?????方法自動阻塞線程,直到count的值變為0
執行過程中,同步操作count后,開始等待,直到100個線程全部創建后并發執行
代碼如下
public class Parallellimit {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
Counts count = new Counts();
count.num = 0;
CountDownLatch cdl = new CountDownLatch(100);
for(int i=0;i<100;i++){
CountRunnable runnable = new CountRunnable(cdl);
pool.execute(runnable);
}
}
}
/**
* 〈countDownlatch實現高并發〉
* @author:???? irvingyuan
* @since?????? 2017年1月23日 下午5:45:59
* @version:
*/
public class CountRunnable implements Runnable {
private CountDownLatch countDownLatch;
public CountRunnable(CountDownLatch countDownLatch){
this.countDownLatch = countDownLatch;
}
public void run() {
try {
/**
* 不加鎖也可以支持,雖然打印出的值不對,但最后計算次數卻是100次
* 說明確實是執行了整整100次才并發,計算次數正確
*/
synchronized (countDownLatch) {
/**
* 每次減少一個容量
*/
countDownLatch.countDown();
System.out.println("thread counts = "+(countDownLatch.getCount()));
}
/**
* 阻塞線程,直到countDown至0
*/
countDownLatch.await();
System.out.println("concurrency counts = "+(100-countDownLatch.getCount()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
測試結果
CPU增長率大約10%左右,相對于wait-notify方式要減少約一半。
綜上,阻塞似乎是最坑爹的一種方式