多執行緒編程1-定義理解與三種實現方式
多執行緒編程
進程與執行緒的理解:
進程: 是程式的一次動態執行過程,它經歷了從程式碼載入、執行到執行完畢的一個完整過程,這個過程就是進程產生、發展到最終消亡的過程;
多進程: 作業系統能同時運行多個進程(程式),由於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());
}
}