多謝 風(fēng)水 同學(xué)提出的問題。我重新研究了下源代碼,糾正下我之前存在的錯(cuò)誤的理論。于是對(duì)之前的文章做了修正,之前對(duì)大家引起的誤導(dǎo)表示非常道歉。
這里對(duì)之前的文章重新進(jìn)行了下梳理和研究,希望大家斧正。
---- 正文 -----
這幾天總是討論線程創(chuàng)建方式的問題,說性能比那個(gè)最優(yōu),哪個(gè)更方便等等。對(duì)于熟悉java的人來說,最先接觸的應(yīng)該是ExecutorService,這種線程池管理的方式操作比較簡單,也廣受大家好評(píng)。但是在很多系統(tǒng)上還有其他的實(shí)現(xiàn)方式,把Thread對(duì)象new出來,放進(jìn)容器里面。這些Thread不停的取Queue(有生產(chǎn)者會(huì)往里面放待處理數(shù)據(jù))里的數(shù)據(jù)進(jìn)行處理,不停進(jìn)行循環(huán)??梢缘絨ueue沒數(shù)據(jù)的時(shí)候進(jìn)行阻塞,直到有數(shù)據(jù)重新開始。
但是這里要說明一點(diǎn)的是,這兩者的原理是一樣的,只不過ExecutorService會(huì)借助Runnable來實(shí)現(xiàn)初始化線程,直到初始化滿為止,后續(xù)的Runnable執(zhí)行使用。具體的源碼分析在后續(xù)的文章中一起分析。
不過對(duì)于自己的實(shí)現(xiàn),和ExecutorService的實(shí)現(xiàn),雖然一樣,但是還是想看兩者的速度如何。于是我做了一個(gè)小實(shí)驗(yàn),來簡單判斷這兩者的區(qū)別
ExecutorService的方式:
public static void ExecutorServiceTest() {
ExecutorService es = Executors.newFixedThreadPool(100);
for(int i = 0;i<100;i++) { // 進(jìn)行初始化各個(gè)線程,所以現(xiàn)用Runnable進(jìn)行補(bǔ)充
es.execute(new Runnable() {
int i = 9;
@Override
public void run() {
int b = i*9;
Math.acos(b);
Math.sin(b);
Math.acos(b);
Math.sin(b);
Math.acos(b);
Math.sin(b);
Math.acos(b);
Math.sin(b);
}
});
}
long time = System.currentTimeMillis();
int count = 10000000;
while(count>0) {
es.execute(new Runnable() {
int i = 9;
@Override
public void run() {
int b = i*9;
Math.acos(b);
Math.sin(b);
Math.acos(b);
Math.sin(b);
Math.acos(b);
Math.sin(b);
Math.acos(b);
Math.sin(b);
}
});
count--;
}
es.shutdown();
System.out.println(System.currentTimeMillis() - time);
}
自定義線程池的方式:
public static void SelfThreadTest() {
final ConcurrentLinkedQueue<Integer> queue1 = new ConcurrentLinkedQueue<>();
final List<Thread> threadlist = new ArrayList<>(100);
for(int i = 0;i<100;i++) {
Thread t = new Thread(new Runnable() {
Integer b ;
boolean next = true;
@Override
public void run() {
while(true) {
b = queue1.poll();
if(b == null) {
if(!next) {return;}
continue;
}
int c = b*9;
Math.acos(b);
Math.sin(b);
Math.acos(b);
Math.sin(b);
Math.acos(b);
Math.sin(b);
Math.acos(b);
Math.sin(b);
next = false;
}
}
});
threadlist.add(t);
}
for(int i = 0;i<100;i++) {
threadlist.get(i).start();
}
int count = 10000000;
long time = System.currentTimeMillis();
while(count>0) {
queue1.offer(count);
count --;
}
while(true) {
if(threadlist.get(99).getState() == Thread.State.TERMINATED) { // 這里沒有做太詳細(xì)的判斷略
break;
}
}
System.out.println(System.currentTimeMillis() - time);
}
這兩個(gè)某次運(yùn)行結(jié)果為:
begin
5302
4347
end
第一行為ExecutorService的運(yùn)行結(jié)果,第二行為自定義線程池的運(yùn)行結(jié)果。每次的運(yùn)行時(shí)間都會(huì)有所差別,但是結(jié)果都是類似的。
是的,時(shí)間還是有挺大差別的,但是這個(gè)差別并不是上次說的" 后續(xù)會(huì)重復(fù)創(chuàng)建線程消耗的時(shí)間” 。因?yàn)楹罄m(xù)是不繼續(xù)生成線程的,ExecutorService中的一個(gè)線程其實(shí)在內(nèi)部也是不停的循環(huán),等待Runnable過來。而這個(gè)Runnable也僅是個(gè)接口實(shí)現(xiàn)了run方法,Thread循環(huán)取這個(gè)run方法進(jìn)行運(yùn)行。而這里的時(shí)間差別應(yīng)該是ExecutorService中的各種線程檢查和判斷損失的時(shí)間(僅猜測(cè)-我們后續(xù)文章一起分析源碼)。至于選擇,大家還是先擇系統(tǒng)的比較好,畢竟可以保證結(jié)果的穩(wěn)定。自定義的方式,大家可以研究研究,如果要用在生產(chǎn)環(huán)境上,務(wù)必需要進(jìn)行足夠量的測(cè)試。
如果對(duì)Java線程創(chuàng)建流程感興趣,可以參考這篇文章:https://yq.aliyun.com/articles/72803 。文章中講到Java的線程創(chuàng)建最終會(huì)通過JNI調(diào)用到系統(tǒng)的pthread_create(C語言線程創(chuàng)建),可見Java的線程就是真實(shí)的線程。