進程與線程
??在Java語言里面最大的特點是支持多線程的開發(fā)(也是為數(shù)不多支持多線程的編程語言),所以在整個Java技術(shù)的學習里面,如果你不能對多線程的概念有一個全面并且細致的了解秘遏,則在日后進行 一些項目設(shè)計尤其是并發(fā)訪問設(shè)計的過程之中就會出現(xiàn)嚴重的技術(shù)缺陷。
??如果想要理解線程近弟,就得先了解進程的概念祝峻,在傳統(tǒng)的DOS系統(tǒng)中,其本身有一個特征:如果你電腦上出現(xiàn)了病毒堵漱,那么所有的程序?qū)o法執(zhí)行综慎,因為傳統(tǒng)的DOS采用的是單線程處理,而單進程處理的最大特點:在同一個時間段上勤庐,只允許一個程序在執(zhí)行示惊。
??那么后來來到了Windows的時代就開啟了許多多線程的設(shè)計,于是就表示在同一個時間段上可以同時運行多個程序愉镰,并且這些將進行資源的輪流搶占米罚。所以在同一個時間段上會有對個程序一次執(zhí)行,但是在同一個時間點上只會有一個進程執(zhí)行丈探,而后來到了多核CPU录择,由于可以處理的CPU多了,即便有再多的進程,也可以比單核CPU處理的速度有所提升隘竭。
??線程是在進程基礎(chǔ)之上劃分的更小的程序單元塘秦,線程是在進程基礎(chǔ)上創(chuàng)建并使用的,所以線程依賴于進程的支持货裹,但是線程的啟動速度要比進程快許多嗤形,所以當使用多線程進行并發(fā)處理時,其執(zhí)行性能要高于進程弧圆。
??Java是多線程的編程語言赋兵,所以Java在進行并發(fā)訪問處理的時候可以得到更高的處理性能。
??如果想在Java中實現(xiàn)多線程的定義搔预,那么就需要有一個專門的線程主體類進行線程的執(zhí)行任務(wù)的定義霹期,而這個主體類的定義是有要求的,不許是實現(xiàn)特定的接口或者繼承特定的父類才可以完成拯田。
繼承Thead類實現(xiàn)多線程
??Java里面有一個java.lang.Thread的程序類历造,那么一個只要繼承了此類就表示這個類為我們線程的主體類,但是并不是說這個類就可以實現(xiàn)多線程處理船庇,因為還需要覆寫Thread類中提供的一個run()方法吭产,而這個方法就屬于線程的主方法。
范例:多線程主體類
class MyThread extends Thread {//線程的主體類
private String title;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {//線程的主體方法
for (int i = 0; i < 10; i++) {
System.out.printf("%s運行.i = %s \n", this.title, i);
}
}
}
??多線程要執(zhí)行的功能都應(yīng)該在run()方法中進行定義鸭轮。
??需要說明的是:在正常情況下臣淤,如果想使用一個類中的方法,那么肯定要產(chǎn)生實例化對象窃爷,而后去調(diào)用類中提供的方法邑蒋,但是run()方法是不能夠直接被調(diào)用的,因為這里面牽扯到操作系統(tǒng)資源調(diào)度問題按厘,所以要想啟動多線程必須使用start()方法完成塞颁。
范例:多線程啟動
public class ThreadDemo {
public static void main(String[] args) {
new MyThread("線程A").start();
new MyThread("線程B").start();
new MyThread("線程C").start();
}
}
??通過此時的調(diào)用你可以發(fā)現(xiàn)试疙,雖然調(diào)用了start()方法疮绷,但是最終執(zhí)行的是run()方法嘱根,并且所有的線程對象都是交替執(zhí)行的。
??疑問:為什么多線程的啟動不直接使用run()方法而必須使用Thread類中的start()方法呢懒棉?如果要想清楚這個問題草描,最好的做法是查看一下start()方法的實現(xiàn)操作,可以直接通過源代碼進行觀察漓藕。
public synchronized void start() {
if (threadStatus != 0) //判斷線程的狀態(tài)
throw new IllegalThreadStateException();//拋出一個異常
group.add(this);
boolean started = false;
try {
start0();//在start()方法中調(diào)用了start0()
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();//只定義了方法名稱陶珠,但沒有實現(xiàn)
??發(fā)現(xiàn)start()方法里面會拋出一個IllegalThreadStateException異常類對象,但是整個程序中并沒有使用throws或者是明確的try..catch處理享钞,因為該異常一定是RuntimeException的子類揍诽,每一個線程類的對象只允許啟動一次诀蓉,如果重復啟動則拋出IllegalThreadStateException異常,例如:下面的代碼就會拋出異常暑脆。
public class ThreadDemo {
public static void main(String[] args) {
MyThread mt= new MyThread("線程A");
mt.start();
mt.start();//重復進行了線程的啟動
}
}
Exception in thread "main" java.lang.IllegalThreadStateException
??在Java程序執(zhí)行的過程之中渠啤,考慮到對于不同層次開發(fā)者的需求,所以其支持有本地的操作系統(tǒng)函數(shù)調(diào)用添吗,而這項技術(shù)就被稱為JNI(Java Native Inteface)技術(shù)沥曹,但是Java開發(fā)過程中并不推薦這樣使用,利用這項技術(shù)可以使用一些操作系統(tǒng)提供的底層函數(shù)碟联,進行一些特處理妓美,而在Thread類中提供的start0()就表示需要將此方法依賴于不同的操作系統(tǒng)實現(xiàn)。
??任何情況下鲤孵,只要定義了多線程壶栋,多線程的啟動永遠只有一種方案:Thread類中的start()方法。
基于Runnable接口實現(xiàn)多線程
??雖然可以通過Thread類的繼承來實現(xiàn)多線程的定義普监,但是在Java程序中對于繼承永遠都是存在單繼承的局限的贵试,所以在Java中又提供第二種多線程的主體定義結(jié)構(gòu)形式:實現(xiàn)java.lang.Runnable接口,此接口定義如下:
@FunctionalInterface //從JDK1.8引入Lambda表達式后就變?yōu)榱撕瘮?shù)式的接口
public interface Runnable{
public void run();
}
范例:通過Runnable實現(xiàn)多線程主體類
class MyThread implements Runnable {//線程的主體類
private String title;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {//線程的主體方法
for (int i = 0; i < 10; i++) {
System.out.printf("%s運行.i = %s \n", this.title, i);
}
}
}
??但是由于此時不再繼承Thread父類了凯正,那么對于此時的MyThread類中也就不再支持有start()這個繼承的方法毙玻,可是如果不用start()方法是無法進行多線程啟動的,那么這個時候就需要去觀察一下Thread類所提供的構(gòu)造方法廊散。
- 構(gòu)造方法:public Thread?(Runnable target);
范例:啟動多線程
public class ThreadDemo {
public static void main(String[] args) {
Thread threadA=new Thread(new MyThread("線程對象A"));
Thread threadB=new Thread(new MyThread("線程對象B"));
Thread threadC=new Thread(new MyThread("線程對象C"));
threadA.start();//啟動多線程
threadB.start();//啟動多線程
threadC.start();//啟動多線程
}
}
??這個時候的多線程實現(xiàn)中可以發(fā)現(xiàn)桑滩,由于只是實現(xiàn)了Runnable接口對象,所以此時線程主體類就不再有單繼承局限奸汇,這樣的設(shè)計才是一個標準型的設(shè)計施符。
??可以發(fā)現(xiàn)從JDK1.8開始往声,Runnable接口使用了函數(shù)式接口定義擂找,所以也可以直接使用Lambda表達式進行線程類實現(xiàn)。
范例:利用Lambda實現(xiàn)多線程定義
public class ThreadDemo {
public static void main(String[] args) {
for (int i = 1; i <= 3; i++) {
String title = "線程對象" + i;
// Runnable run = () -> {
// for (int j = 0; j < 10; j++) {
// System.out.printf("%s運行.j = %s \n", title, j);
// }
// };
// new Thread(run).start();
new Thread(() -> {
for (int j = 0; j < 10; j++) {
System.out.printf("%s運行.j = %s \n", title, j);
}
}).start();
}
}
}
??在以后的開發(fā)之中對于多線程的實現(xiàn)浩销,優(yōu)先考慮的就是Runnable接口實現(xiàn)贯涎,并且通過Thread類啟動多線程。
Thread與Runnable關(guān)系
??經(jīng)過一些列的分析之后可以發(fā)現(xiàn)慢洋,在多線程的實現(xiàn)過程之中已經(jīng)有了兩種做法:Thread類塘雳、Runnable接口,如果從代碼本身來講普筹,肯定是用Runnable是最方便的败明,因為其可以避免單繼承的局限,同事也可以更好的進行功能的擴充太防。
??但是從結(jié)構(gòu)上也需要來觀察Thread和Runnable的聯(lián)系妻顶,打開Thead的定義:
public class Thread extends Object implements Runnable {}
??發(fā)現(xiàn)Thread類也是Runnable接口的子類,所以繼承Thread類時覆寫的還是Runnable接口的run(),于是此時觀察一下程序的類結(jié)構(gòu)讳嘱。
class MyThread implements Runnable {//線程的主體類
private String title;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {//線程的主體方法
for (int i = 0; i < 10; i++) {
System.out.printf("%s運行.i = %s \n", this.title, i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Thread threadA=new Thread(new MyThread("線程對象A"));
Thread threadB=new Thread(new MyThread("線程對象B"));
Thread threadC=new Thread(new MyThread("線程對象C"));
threadA.start();//啟動多線程
threadB.start();//啟動多線程
threadC.start();//啟動多線程
}
}
??多線程的設(shè)計之中幔嗦,使用了代理設(shè)計模式的結(jié)構(gòu),用戶自定義的線程主體只是負責項目核心的實現(xiàn)沥潭,而所有輔助實現(xiàn)全部由Thread類處理邀泉。
??在進行Thread啟動多線程的時候調(diào)用的是start()方法,而后找到的是run()方法钝鸽,但通過Thread類的構(gòu)造方法傳遞了一個Runnable接口對象時汇恤,那么該接口對象將被Thread類中的target屬性所保存,在start()方法執(zhí)行時會調(diào)用Thread中的run()方法拔恰,而這個run()方法會去調(diào)用Runnable接口子類被覆寫過的run()方法屁置。
??多線程開發(fā)的本質(zhì)實質(zhì)上是在于多個線程可以進行同一資源的搶占,那么Thread主要描述的是線程仁连,而資源的描述是通過Runnable完成的蓝角。
范例:利用賣票程序來實現(xiàn)多個線程的資源并發(fā)訪問
class MyThread implements Runnable {//線程的主體類
private int ticket = 5;
@Override
public void run() {//線程的主體方法
for (int i = 0; i < 100; i++) {
if (this.ticket > 0)
System.out.printf("賣票,ticket = %s \n", this.ticket--);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt).start();//第一個線程啟動
new Thread(mt).start();//第二個線程啟動
new Thread(mt).start();//第三個線程啟動
}
}
??通過內(nèi)存分析圖來分析本程序的執(zhí)行結(jié)構(gòu)饭冬。
Callable實現(xiàn)多線程
??從最傳統(tǒng)的開發(fā)來說使鹅,如果要進行多線程的實現(xiàn)肯定依靠Runnable,但是Runnable接口有一個缺點昌抠,當Runnable執(zhí)行完成后無法獲取一個返回值患朱,所以從JDK1.5后提出了一個新的線程實現(xiàn)接口:java.util.concurrent.Callable接口,首先來觀察這個接口的定義:
@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception;
}
??可以發(fā)現(xiàn)Callable定義的時候可以設(shè)置一個泛型炊苫,此泛型的類型就是返回數(shù)據(jù)的類型裁厅,這樣的好處在于避免向下轉(zhuǎn)型所帶來的的安全隱患。
范例:使用Callable實現(xiàn)多線程處理
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String> {//線程的主體類
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("*********** 線程侨艾、i = " + i);
}
return "線程執(zhí)行完畢执虹。";
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
FutureTask<String> task = new FutureTask(new MyThread());
new Thread(task).start();
System.out.println("【線程返回數(shù)據(jù)】" + task.get());
}
}
線程運行狀態(tài)
??對于多線程的開發(fā)而言,編寫程序的過程之中總是按照:定義線程主體類唠梨,然后通過Thread類進行線程的啟動袋励,但是并不意味著你調(diào)用了start()方法,線程就已經(jīng)開始運行了当叭,因為整體的線程處理有自己的一套運行的狀態(tài)茬故。
1、任何一個線程的對象都應(yīng)該使用Thread類進行封裝蚁鳖,所以線程的啟動使用的是start()磺芭,但是啟動的時候?qū)嶋H上若干個線程都將進入到一種就緒狀態(tài),現(xiàn)在并沒有執(zhí)行醉箕;
2钾腺、進入到就緒狀態(tài)后就需要等待進行資源調(diào)度甘邀,當某一個線程調(diào)度成功之后則進入到運行狀態(tài)(run()方法),但是所有的線程不可能一直持續(xù)執(zhí)行下去垮庐,中間需要產(chǎn)生一些暫停的狀態(tài)松邪,例如:某個線程執(zhí)行一段時間之后就將需要讓出資源,而后這個線程就將進入到阻塞狀態(tài)哨查,隨后重新回歸到就緒狀態(tài)逗抑。
3寒亥、當run()方法執(zhí)行完畢之后溉奕,實際上該線程的主要任務(wù)也就結(jié)束了,那么此時就可以直接進入到停止狀態(tài)加勤。