進程與線程
1.進程:進程是指一個內存中運行的應用程序,比如在Windows系統中,一個運行的exe就是一個進程。
2.線程:進程是負責程序執行的執行單元,線程本身依靠程序進行運行。
3 單線程:程序中只存在一個線程,實際上主方法就是一個主線程。
4 多線程:在一個程序中運行多個任務,目的是更好地使用CPU資源。
線程的生命周期
關于Java中線程的生命周期,首先看一下下面這張較為經典的圖:
上圖中基本上囊括了Java中多線程各重要知識點。掌握了上圖中的各知識點,Java中的多線程也就基本上掌握了。主要包括:
Java線程具有五中基本狀態
新建狀態(New):當線程對象對創建后,即進入了新建狀態,如:Thread t = new MyThread();
就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處于就緒狀態的線程,只是說明此線程已經做好了準備,隨時等待CPU調度執行,并不是說執行了t.start()此線程立即就會執行;
運行狀態(Running):當CPU開始調度處于就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就 緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處于就緒狀態中;
阻塞狀態(Blocked):處于運行狀態中的線程由于某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:
1.等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;
2.同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態;
3.其他阻塞 -- 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
4死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。
4種線程實現的方式
1.繼承Thread類
在java.lang包中定義, 繼承Thread類必須重寫run()方法
class MyThread extends Thread{
private static int num = 0;
public MyThread(){
num++;
}
@Override
public void run() {
System.out.println("主動創建的第"+num+"個線程");
}
}
創建好了自己的線程類之后,就可以創建線程對象了,然后通過start()方法去啟動線程。注意,不是調用run()方法啟動線程,run方法中只是定義需要執行的任務,如果調用run方法,即相當于在主線程中執行run方法,跟普通的方法調用沒有任何區別,此時并不會創建一個新的線程來執行定義的任務
public class ThreadTest {
public static void main(String[] args) {
MyThread thread=new MyThread();
thread.start();
}
}
2實現Runnable接口
在Java中創建線程除了繼承Thread類之外,還可以通過實現Runnable接口來實現類似的功能。實現Runnable接口必須重寫其run方法。
下面是一個例子:
public class Test {
public static void main(String[] args) {
System.out.println("主線程ID:"+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
public MyRunnable() {
}
@Override
public void run() {
System.out.println("子線程ID:"+Thread.currentThread().getId());
}
}
Runnable的中文意思是“任務”,顧名思義,通過實現Runnable接口,我們定義了一個子任務,然后將子任務交由Thread去執行。注意,這種方式必須將Runnable作為Thread類的參數,然后通過Thread的start方法來創建一個新線程來執行該子任務。如果調用Runnable的run方法的話,是不會創建新線程的,這根普通的方法調用沒有任何區別。
事實上,查看Thread類的實現源代碼會發現Thread類是實現了Runnable接口的。
在Java中,這2種方式都可以用來創建線程去執行子任務,具體選擇哪一種方式要看自己的需求。直接繼承Thread類的話,可能比實現Runnable接口看起來更加簡潔,但是由于Java只允許單繼承,所以如果自定義類需要繼承其他類,則只能選擇實現Runnable接口。
3使用執行器(Executor)創建線程池(thread pool)
從JDK1.5開始,java.util.concurrent包中的執行器(Executor)將為你管理Thread對象,從而簡化了并發編程。如果我們的程序需要用到很多生命周期比較短的線程,那么應該使用線程池,線程池中包含了很多空閑線程,而且這些線程的生命周期不需要我們操心。另一個使用線程池的原因是:如果你的代碼需要大量的線程,那么最好使用一個線程池來規定總線程數的上線,防止虛擬機崩潰。這樣可以限制最大的并發數量。
Java通過Executors提供四種線程池,分別為:
newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
newFixedThreadPool 創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
(1) newCachedThreadPool
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。示例代碼如下:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(index);
}
});
}
}
}
線程池為無限大,當執行第二個任務時第一個任務已經完成,會復用執行第一個任務的線程,而不用每次新建線程。
(2) newFixedThreadPool
創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。示例代碼如下:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
因為線程池大小為3,每個任務輸出index后sleep 2秒,所以每兩秒打印3個數字。
定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors();
(3) newScheduledThreadPool
創建一個定長線程池,支持定時及周期性任務執行。延遲執行示例代碼如下:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
}
}
表示延遲3秒執行。
定期執行示例代碼如下:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
}
}
表示延遲1秒后每3秒執行一次。
(4) newSingleThreadExecutor
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。示例代碼如下:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
結果依次輸出,相當于順序執行各個任務。
你可以使用JDK自帶的監控工具來監控我們創建的線程數量,運行一個不終止的線程,創建指定量的線程,來觀察:
工具目錄:C:\Program Files\Java\jdk1.8.0_06\bin\jconsole.exe
運行程序做稍微修改:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
while(true) {
System.out.println(index);
Thread.sleep(10 * 1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.使用Callable與Future創建線程并獲取返回值
我們使用Runnable封裝了一個異步運行的任務,我們可以把它想象成一個沒有參數和返回值的異步方法,Callable與Runnable相似,但是Callable具有返回值,可以從線程中返回數據。
Callable
我們從jdk中找到了Callable<V>的源代碼,去掉一些無用的部分:
package java.util.concurrent;
public interface Callable<V> {
V call() throws Exception;
}
可以看出Callable接口只是將run方法換成了call方法,其他并沒有太多的改動。
Future
Future類負責保存異步計算的結果。可以啟動一個計算,將Future對象交給某個線程,然后我們去做其他的事情,Future對象的所有者在結果計算好之后就可以調用get方法獲得它。我們在上面線程池的submit方法中也提到過。
V get() throws InterruptedException,ExecutionException
如有必要,等待計算完成,然后通過get方法獲取其結果。
FutureTask包裝器
我們雖然有了Callable和Future類,但是我們仍然需要一種方法將他們結合起來使用。而且還存在的問題是,Callable的出現替代了Runnable。我們需要一種手段讓Thread類能夠接受Callable做參數。在這里我們使用非常好用的FutureTask包裝器。它可以將Callable轉換成Futrue和Runnable,因為他同時實現了Runnable和Future<V>兩個接口。
代碼實現:
public class CallablePool {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService es = Executors.newFixedThreadPool(5);
// 創建一個5個線程大小的線程池
ArrayList<Future<Integer>> results = new ArrayList<Future<Integer>>(5);
// 創建一個Future<Integer>類型的數組
FutureTask<Integer> ft = null;
for (int i = 0; i < 5; i++) {
ft = new FutureTask<Integer>(new ImplCallable(i));
// 將Callable類型轉化成FutureTask類型
es.submit(ft);
// 提交線程
results.add(ft);
// 將返回的結果提交,因為FutureTask同時也可變為Future類型,所以這里不需要其他類型轉化
}
for (int i = 0; i < 5; i++)
try {
System.out.println(results.get(i).get());
// 打印結果,發現數組中為0到4五個數字,成功。
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class ImplCallable implements Callable<Integer> {
private int index;
ImplCallable(int index) {
this.index = index;
}
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
return index;
}
}
上面采用了線程池的方法來表現Callable和Future的使用方法,如果是簡單實用Thread道理也是相同的,我們需要把:
ExecutorService es = Executors.newFixedThreadPool(5);
es.submit(ft);
這兩句去掉,在for循環中替換成下面兩句。創建Thread實例,開啟新的線程。
Thread th = new Thread(ft);
th.start();