多執行緒程式設計
程式與執行緒的理解:
程式: 是程式的一次動態執行過程,它經歷了從程式碼載入、執行到執行完畢的一個完整過程,這個過程就是程式產生、發展到最終消亡的過程;
多程式: 作業系統能同時執行多個程式(程式),由於CPU
具備分時機制,在每個程式都能迴圈獲得自己的CPU時間片;由於CPU執行的速度非常快,使得所有的程式好像是在同時執行一樣。
程式與執行緒的區別與聯絡:
- 程式是資源排程的基本單位,執行一個可執行程式會建立一個或多個執行緒,程式就是執行起來的可執行程式;
- 執行緒是程式執行的基本單位,是輕量級的程式,每個程式中都有唯一的主執行緒,且只能有一個,主執行緒和程式是相互依存的關係,主執行緒結束,程式也會結束。
具體例項(word):
每次啟動Word對於作業系統而言就相當於啟動了一個系統的程式,而在這個程式之上又有許多其他程式在執行(拼寫檢查等),那麼對於這些程式就是一個個多執行緒。如果Word關閉了,則這些拼寫檢查的執行緒也肯定會消失,但是如果拼寫檢查的執行緒消失了,並不一定會讓Word的程式消失;
多插一句:如果開啟兩個word文件,則表示當前作業系統建立了兩個程式。
多執行緒實現:
實現多執行緒需要一個執行緒的主體類,這個類可以繼承Thread
、實現Runnable
以及Callable
介面完成定義;
Thread實現多執行緒:
繼承結構如下:
public class Thread extends Object implements Runnable
實現介面Runnable
,所以必須實現介面中的抽象方法:
Modifier and Type | Method | Description |
---|---|---|
void |
run() |
當一個實現介面Runnable的物件被用來建立執行緒時,啟動執行緒會導致物件的run方法在單獨執行的執行緒中被呼叫。 |
void |
start() |
使執行緒開始執行;Java虛擬機器呼叫這個執行緒的run方法。 |
當產生多個物件時,這些物件就會併發的執行run()方法中的程式碼;
雖然多執行緒的執行方法都在run()方法中定義,但是在實際進行多執行緒啟動時並不能直接呼叫此方法,由於多執行緒時需要併發執行的,所以需要通過作業系統的資源排程才能執行,這樣多執行緒的啟動就必須利用Thread類中的start()方法完成,呼叫此方法會間接的呼叫run()方法。
例項:
package Java從入門到專案實戰.多執行緒程式設計.Java多執行緒實現;
class MyThread extends Thread{ //單繼承
private String title;
public MyThread(String title){
this.title = title;
}
//覆寫執行緒的run方法
@Override
public void run() {
for (int i = 0 ; i < 10; i++){
System.out.println(this.title+"執行,i =" +i);
}
}
}
public class Main{
public static void main(String[] args){
new MyThread("執行緒A").start(); //例項化執行緒物件並啟動
new MyThread("執行緒B").start();
new MyThread("執行緒C").start();
//對照
/*沒有開啟多執行緒*/
new MyThread("執行緒A").run();
new MyThread("執行緒B").run();
new MyThread("執行緒C").run();
}
}
由效果圖可以看出,三個執行緒在交替執行:
假如面試題:
為什麼執行緒啟動的時候必須呼叫start()方法而不是直接呼叫run()方法?
在本程式中,程式呼叫了Thread類繼承而來的start()方法後,實際上他執行的還是覆寫後的run()方法,那為什麼不直接呼叫run()?
簡單的說下:是因為多執行緒需要呼叫作業系統的資源,在start()下有一個關鍵的部分start0()方法,並且在start0()方法上使用了navite關鍵字定義;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0(); //navite
什麼是navite?
navite是指:Java本機介面(Java Native Interface)簡稱:JNI;使用Java呼叫本機作業系統的函式功能完成一些特殊操作;
在Java中將start0()方法體交給JVM進行實現,所以這樣就會出現在windows或者在Linux中實現的start0()的是不同,不關係過程,只關心結果(是否呼叫了本機的作業系統的函式);
start0()作用:交由JVM進行匹配不同的作業系統,實現start0()方法體,功能:實現本機函式的呼叫;
具體百度、Google吧。
Runnable介面實現多執行緒:
出現的原因:為了解決Thread實現多執行緒出現的單繼承問題;並且增加了函式式介面;
Modifier and Type | Method | Description |
---|---|---|
void |
run() |
當一個實現介面Runnable的物件被用來建立執行緒時,啟動執行緒會導致物件的run方法在單獨執行的執行緒中被呼叫。 |
實現程式碼:
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.println(this.title+"執行,i"+i);
}
}
}
啟動方式一:
Thread threadA = new Thread(new MyThread("執行緒A"));
Thread threadB = new Thread(new MyThread("執行緒B"));
Thread threadC = new Thread(new MyThread("執行緒C"));
Thread threadD = new Thread(new MyThread("執行緒D"));
Thread threadE = new Thread(new MyThread("執行緒E"));
threadA.start();
threadB.start();
threadC.start();
threadD.start();
threadE.start();
啟動方式二:
//通過Lambal表示式定義執行緒主體
for(int x = 0; x < 3;x++){
String title = "執行緒物件-"+x;
//實際上Thread傳入的型別是Runnable
new Thread(()->{ //Lambda實現執行緒主體
for(int y = 0; y < 20; y++){
System.out.println(title+"執行,y"+y);
}
}).start();
}
Thread與Runnable的聯絡:
繼承結構:
public class Thread extends Object implements Runnable
在之前繼承Thread類的時候實際上覆寫的還是Runnable介面的run()方法。
實現併發訪問資源:
package Java從入門到專案實戰.多執行緒程式設計.Java多執行緒實現;
class MyThreadConcurrent implements Runnable {
private int ticket = 5;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//同步操作--》從5-1票數
/*synchronized(this){
if(this.ticket > 0){
System.out.println("賣票,ticket = "+this.ticket--);
}
}*/
//票數亂數
if(this.ticket > 0){
System.out.println("賣票,ticket = "+this.ticket--);
}
}
}
}
public class 併發資源訪問 {
public static void main(String[] args) {
MyThreadConcurrent thread = new MyThreadConcurrent();
new Thread(thread).start(); //第一個執行緒
new Thread(thread).start(); //第二個執行緒
new Thread(thread).start(); //第三個執行緒
}
}
總結一句話:Thread有單繼承的侷限性以及在有些情況下結構的不合理性;所以後面多執行緒的實現使用的都是Runnable介面。
Callable介面實現多執行緒:
為什麼要使用Callable介面來實現多執行緒?
因為使用Callable介面實現彌補了Runnable實現多執行緒沒有返回值的問題。
繼承結構如下:
@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception{
}
}
定義的時候可以設定一個泛型,此泛型的型別就是call()方法的返回的資料型別,好處:可以避免向下轉型的安全隱患。
執行緒類主體完成後,需要啟動多執行緒的話還是需要通過Thread類實現的,又因為我們的Callable介面與Thread沒有聯絡,所以我們需要FutureTask類實現兩者之間的聯絡;如圖所示:
通過FutureTask類繼承結構可以發現它是Runnable介面的子類;
程式碼實現如下:
package Java從入門到專案實戰.多執行緒程式設計.Java多執行緒實現;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class CallableThread implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("執行緒執行 x = "+i);
}
return "xbhog";
}
}
public class Callable介面實現多執行緒 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//將Callable例項化包裝在FutureTask類中,這樣就可以與Runnable介面關聯
FutureTask<String> task = new FutureTask<String>(new CallableThread());
//執行緒啟動
new Thread(task).start();
//獲取call()的返回值
System.out.println("【執行緒返回資料】:"+task.get());
}
}