【java併發】執行緒併發庫的使用
1. 執行緒池的概念 |
在java5之後,就有了執行緒池的功能了,在介紹執行緒池之前,先來簡單看一下執行緒池的概念。假設我開了家諮詢公司,那麼每天會有很多人過來諮詢問題,如果我一個個接待的話,必然有很多人要排隊,這樣效率就很差,我想解決這個問題,現在我僱幾個客服,來了一個諮詢的,我就分配一個客服去接待他,再來一個,我再分配個客服去接待……如果第一個客服接待完了,我就讓她接待下一個諮詢者,這樣我僱的這些客服可以迴圈利用。這些客服就好比不同的執行緒,那麼裝這些執行緒的容器就稱為執行緒池。
2. Executors的使用 |
Executors工具類是用來建立執行緒池的,這個執行緒池可以指定執行緒個數,也可以不指定,也可以指定定時器的執行緒池,它有如下常用的方法:
方法名 | 作用 |
---|---|
newFixedThreadPool(int nThreads) | 建立固定數量的執行緒池 |
newCachedThreadPool() | 建立快取的執行緒池 |
newSingleThreadExecutor() | 建立單個執行緒 |
newScheduledThreadPool(int corePoolSize) | 建立定時器執行緒池 |
2.1 固定數量的執行緒池
先來看下Executors工具類的使用:
public class ThreadPool {
//執行緒池的概念與Executors類的使用
public static void main(String[] args) {
//固定執行緒池:建立固定執行緒數去執行執行緒的任務,這裡建立三個執行緒
ExecutorService threadPool = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 10; i++) {//向池子裡扔10個任務
final int task = i;
threadPool.execute(new Runnable() {//execute方法表示向池子中扔任務,任務即一個Runnable
@Override
public void run() {
for (int j = 1; j <= 5; j++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " looping of " + j + " for task of " + task);
}
}
});
}
System.out.println("all of 10 tasks have committed!");
threadPool.shutdown(); //執行完任務後關閉
// threadPool.shutdownNow(); //立即關閉
}
}
從程式碼中可以看出,有了Executors工具類,我們建立固定數量的執行緒數就方便了,這些執行緒都去做同樣的任務。threadPool.execute表示從池子裡取出一個執行緒去執行任務,上面定義了三個執行緒,所以每次會取三個任務去讓執行緒執行,其他任務等待,執行完後,再從池子裡取三個任務執行,執行完,再取三個任務,最後一個任務三個執行緒有一個會搶到執行。所以定義了執行緒數量的話,每次會執行該數量的任務,因為一個執行緒一個任務,執行完再執行其他任務。
因為這個執行結果有點多,就不貼結果了,反正每次三個任務一執行,直到執行完10個任務為止。
2.2 快取執行緒池
所謂快取執行緒池,指的是執行緒數量不固定,一個任務來了,我開啟一個執行緒為其服務,兩個任務我就開啟兩個,N個任務我就開啟N個執行緒為其服務。如果現在只剩1個任務了,那麼一段時間後,就把多餘的執行緒給幹掉,保留一個執行緒為其服務。所以可以改寫一下上面的程式碼:
public class ThreadPool {
//執行緒池的概念與Executors類的使用
public static void main(String[] args) {
//快取的執行緒池
//自動根據任務數量來設定執行緒數去服務,多了就增加執行緒數,少了就減少執行緒數
//這貌似跟一般情況相同,因為一般也是一個執行緒執行一個任務,但是這裡的好處是:如果有個執行緒死了,它又會產生一個新的來執行任務
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 1; i <= 10; i++) {//扔5個任務
final int task = i;
threadPool.execute(new Runnable() {//向池子中扔任務,任務即一個Runnable
@Override
public void run() {
for (int j = 1; j <= 5; j++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " looping of " + j + " for task of " + task);
}
}
});
}
System.out.println("all of 10 tasks have committed!");
threadPool.shutdown(); //執行完任務後關閉
}
}
使用快取執行緒池的時候,會自動根據任務數量來產生執行緒數,即執行緒跟著任務走。執行結果也不貼了,有點多。
那麼建立單個執行緒池newSingleThreadExecutor()就寫了,把上面那個方法改掉就行了,就只有一個執行緒去執行10個任務了,但是這跟我們平常直接new一個執行緒還有啥區別呢?它還有個好處就是,如果執行緒死了,它會自動再生一個,而我們自己new的就不會了。如果執行緒死了還要重新產生一個,也就是說要保證有一個執行緒在執行任務的話,那麼newSingleThreadExecutor()是個很好的選擇。
3. 執行緒池啟動定時器 |
我們可以用靜態方法newScheduledThreadPool(int corePoolSize)來定義一個定時器執行緒池,可以指定執行緒個數。然後再呼叫schedule方法,傳進去一個Runnable和定時時長即可,見程式碼:
public class ThreadPool {
public static void main(String[] args) {
Executors.newScheduledThreadPool(3).schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " bombing");
}
}, 2, TimeUnit.SECONDS);
}
}
定義了三個執行緒,會有一個率先搶到任務執行在2秒後執行。這只是建立了一個任務,如果我們要使用這個執行緒池去執行多個任務咋辦呢?schedule中只能傳入一個Runnable,也就是說只能傳入一個任務,解決辦法跟上面那些程式一樣,先拿到建立的執行緒池,再迴圈多次執行schedule,每次都傳進去一個任務即可:
public class ThreadPool {
public static void main(String[] args) {
//拿到定時器執行緒池
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
for(int i = 1; i <= 5; i ++) { //執行5次任務
threadPool.schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " bombing");
}
}, 2, TimeUnit.SECONDS);
}
}
}
因為執行緒池中只有3個執行緒,但是有5個任務,所以會先執行3個任務,剩下兩個任務,隨機2個執行緒執行,看下結果:
pool-1-thread-3 bombing
pool-1-thread-2 bombing
pool-1-thread-1 bombing
pool-1-thread-2 bombing
pool-1-thread-3 bombing
如果我想5秒後執行一個任務,然後每個2秒執行一次該怎麼辦呢?我們可以呼叫另一個方法scheduleAtFixedRate,這個方法中傳進去一個Runnable,一個定時時間和每次重複執行的時間間隔,如下:
public class ThreadPool {
public static void main(String[] args) {
Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " bombing");
}
}, 5, 2, TimeUnit.SECONDS);
}
}
這樣就可以5秒後執行,並且以後每隔2秒執行一次了。這個方法有個瑕疵,就是無法設定指定時間點執行,官方JDK提供的解決辦法是data.getTime()-System.currentTimeMillis()
來獲取相對時間,然後放到上面方法的第二個引數即可。
執行緒併發庫的使用就總結這麼多吧~