在一個進程上可以劃分出若干個線程,那麼線程的操作一定要比進程更快些,所以多線程的操作性能要超過多進程的操作,但是所有的線程都一定是要在進程的基礎之上進行劃分。
所以進程一旦消失,線程一定消失。線程依附於進程存在。
一.多線程的實現
在java中對於多線程實現一定要有一個線程的類。
線程主類的實現必須繼承Thread父類或實現Runnable接口(Callable接口)
繼承Thread類實現多線程
在java中存在有Thread類,子類繼承Thread類之後需要覆寫Thread類的Run()方法,這個Run()方法就屬於線程的主方法。Run()的定義:public void run()
範例:實現線程的主體類
class MyThread extends Thread{ //表示實現多線程 private String name; public MyThread(String name){ //線程名字 this.name = name; } @Override public void run() { //覆寫Run()方法,線程的主方法 for(int i=0;i<10;i++){ System.out.println(this.name+";i= "+i); } } }
在線程的主類之中只是將內容輸入10次,但是需要注意:所有的多線程的執行一定是並非完成的,既:在同一個時間段上有多個線程交替執行,所以爲了達到這樣的目的,絕對不能直接調用Run()方法,而是通過調用Thread類中的start()方法啓動多線程。
範例:啓動多線程
public class test { public static void main(String[] args) { MyThread mt1 = new MyThread("線程A"); MyThread mt2 = new MyThread("線程B"); MyThread mt3 = new MyThread("線程C"); mt1.start(); mt2.start(); mt3.start(); } }
所有的線程都屬於交替執行,本身是沒有執行順序的。
爲什麼現在啓動多線程不使用Run()方法,而非要使用start()方法,需要了解可以從Thread類中的start()源碼來解析(java安裝目錄下的源碼文件src.zip)。
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();
從代碼可以看出,方法會拋出一個異常:IllegalThreadStateException,但是整個方法沒有使用throws聲明,沒有使用try...catch()捕獲處理,因爲該異常屬於RuntimeException的子類。
java.lang.Object
- java.lang.Throwable
- java.lang.Exception
- java.lang.RuntimeException
- java.lang.IllegalArgumentException
- java.lang.IllegalThreadStateException
此異常是指一個線程已經調用了start()方法後又重複執行start()方法所造成的。
在調用start()方法裏面發現會調用start0()方法,而start0()方法上使用了native關鍵字定於,這個關鍵字指的是要調用本機的操作系統函數。
總結:只有Thread類的start()方法才能夠進行操作系統資源的分配,所以啓動多線程的方式永遠是調用Thread類的start()方法實現。
2.Runnable接口實現多線程。
使用Thread類會產生單繼承的侷限性,所以最好使用Runnable接口來實現多線程。
Runnable接口的定義結構:
@FunctionalInterface public interface Runnable { public void run(); }
@FunctionalInterface 表示函數式接口,所以可以利用lamda表達式完成。
範例:正常思路實現多線程
class MyThread implements Runnable{ //表示實現多線程 private String name; public MyThread(String name){ //線程名字 this.name = name; } @Override public void run() { //覆寫Run()方法,線程的主方法 for(int i=0;i<10;i++){ System.out.println(this.name+";i= "+i); } } }
要想啓動多線程只能是依靠Thread類中的start()方法,之前繼承Thread類時可以直接將start()方法繼承下來使用。
但現在實現的是Runnable接口,沒有start()方法了,所以要想啓動多線程,需使用到Thread類中的構造方法:public Thread(Runnable target) 此構造方法可以接受Runnable的子類對象。
啓動多線程如下:
public class test { public static void main(String[] args) { MyThread mt1 = new MyThread("線程A"); MyThread mt2 = new MyThread("線程B"); MyThread mt3 = new MyThread("線程C"); new Thread(mt1).start(); new Thread(mt2).start(); new Thread(mt3).start(); } }
爲了方便實現,可以直接使用匿名內部類或者是Lamda實現代碼。
範例:匿名內部類實現
public class test { public static void main(String[] args) { String name = "線程對象"; new Thread(new Runnable(){ @Override public void run() { for(int i=0;i<10;i++){ System.out.println(name+";i= "+i); } } }).start(); } }
範例:jdk1.8使用Lamda實現
public class test { public static void main(String[] args) { String name = "線程對象"; new Thread(()->{ for(int i=0;i<10;i++){ System.out.println(name+";i= "+i); } }).start(); } }
只要給出的是函數式接口基本上就可以使用Lamda表達式或者是方法引用。
3.Thread類和Runnable接口實現方式的區別
首先來觀察Thread類的定於結構:
public class Thread extends Object implements Runnable
可以看出Thread類實現了Runnable接口。
除了以上的繼承關聯之外還有一點區別:Runnable接口實現的多線程要比Thread類實現的多線程更方便的表示出數據共享的概念。
範例:希望有三個線程進行賣票--Thread實現。
package org.com; class MyThread extends Thread{ //表示實現多線程 private int ticket=5; @Override public void run() { //覆寫Run()方法,線程的主方法 for(int i=0;i<50;i++){ if(this.ticket>0){ System.out.println("賣票; ticket = "+this.ticket--); } } } } public class test { public static void main(String[] args) { MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); MyThread mt3 = new MyThread(); mt1.start(); mt2.start(); mt3.start(); } }
三個棧內存對應着三個堆內存,所以 輸出:
賣票; ticket = 5
賣票; ticket = 5
賣票; ticket = 5
賣票; ticket = 4
賣票; ticket = 4
賣票; ticket = 3
賣票; ticket = 2
賣票; ticket = 1
賣票; ticket = 4
賣票; ticket = 3
賣票; ticket = 2
賣票; ticket = 1
賣票; ticket = 3
賣票; ticket = 2
賣票; ticket = 1
範例:Runnable實現
package org.com; class MyThread implements Runnable{ //表示實現多線程 private int ticket=5; @Override public void run() { //覆寫Run()方法,線程的主方法 for(int i=0;i<50;i++){ if(this.ticket>0){ System.out.println("賣票; ticket = "+this.ticket--); } } } } public class test { public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt).start(); new Thread(mt).start(); new Thread(mt).start(); } }
輸出:
賣票; ticket = 5
賣票; ticket = 3
賣票; ticket = 4
賣票; ticket = 1
賣票; ticket = 2
總結:使用Runnable接口可以比Thread類更好的實現數據共享操作,並且使用Runnable接口可以避免單侷限性問題。
4.實現Callable接口
Callable接口是從JDK1.5之後開始有的。這個接口裏面比Runnable接口唯一的強大之處在於它可以返回執行結果。此接口在java.util.concurrent包中定義。
@FunctionalInterface public interface Callable<V> { public V call() throws Exception; }
泛型表示的是返回值類型。call()方法就相當於run()方法。
範例:定於線程主題類。
class MyThread implements Callable<String>{ //表示實現多線程 private int ticket=5; @Override public String call() { //覆寫Run()方法,線程的主方法 for(int i=0;i<50;i++){ if(this.ticket>0){ System.out.println("賣票; ticket = "+this.ticket--); } } return "票賣完了"; } }
由於Thread類中並沒有提供接收Callable接口的對象操作,所以要啓動多線程,需要通過一些繼承結構。
java.util.concurrent.FutureTask<V>的定於結構:
public class FutureTask<V>extends Objectimplements RunnableFuture<V> FutureTask<V>實現了RunnableFuture接口,java.util.concurrent.RunnableFuture<V>的定於結構: public interface RunnableFuture<V>extends Runnable, Future<V> 繼承了Runnable接口和Future<V>接口,而Future<V>接口裏有get()方法。
所以啓動多線程範例:
public class test { public static void main(String[] args) throws Exception { Callable<String> cal = new MyThread(); FutureTask<String> task = new FutureTask<>(cal);//取得執行結果 Thread thread = new Thread(task); thread.start(); System.out.println(task.get()); //取得線程主方法的返回值 } }
輸出:
賣票; ticket = 5
賣票; ticket = 4
賣票; ticket = 3
賣票; ticket = 2
賣票; ticket = 1
票賣完了