Java多線程入門

我是阿福,公衆號「JavaClub」做者,一個在後端技術路上摸盤滾打的程序員,在進階的路上,共勉!javascript

文章已收錄在 JavaSharing 中,包含Java技術文章,面試指南,資源分享。java

Java多線程入門

文章主要涉及線程的啓動,如何使多線程暫停,如何使多線程中止,線程的優先級級線程安全相關的問題。git

1.1 進程和多線程的概念及線程的優勢

進程:進程是操做系統結構的基礎,是一次程序的執行,是一個程序及其數據在處理機上順序執行時所發生的活動,是程序在一個數據集合上運行的過程,它是系統進行資源分配和調度的獨立單位。(來源:百度百科)程序員

看了這段話是否是十分抽象,很差理解,可是若是你看到圖所示的內容,你還對進程不理解嗎? NO, NO, NOgithub

難道一個正在操做系統中運行的exe程序能夠理解成一個進程嗎?沒錯!是他!是他!就是他!面試

那麼在Windows任務管理器列表中,徹底能夠將運行在內存中的exe文件理解成進程,進程是受操做系統管理的基本運行單元編程

那什麼是線程呢?後端

線程能夠理解成是進程中獨立運行的子任務,好比 QQ.exe運行時有好多子任務在同時運行。好比:視頻聊天線程,下載文件線程,傳輸數據線程等。這些不一樣的任務徹底能夠同時在運行,其中每一項任務能夠理解成不一樣的線程在工做。安全

那麼這樣的優勢是什麼呢?例如咱們使用多任務操做系統Windows後,能夠最大限度地利用CPU的空閒時間來處理其餘的任務,好比一邊讓操做系統處理正在由打印機打印的數據,一邊使用Word編輯文檔,而CPU在這些任務之間不停地切換,因爲速度很是快,給讀者的感覺就是這些任務彷佛在同時運行。服務器

爲了更好理解多線程的優點,首先咱們經過單利模型圖來理解一下單任務的缺點。

任務1和任務2是兩個徹底獨立,互不相干的任務,任務一是在等待遠程服務器返回數據,以便進行後期的處理,這是CPU一直處於等待狀態,一直在空運行。而任務2一直處於等待狀態必須等任務1返回數據才能運行,這樣系統的運行效率大幅下降。單任務的特色就是排隊執行,也就是同步,就像在cmd中輸入一條命令後,必須等待這條命令執行完才能夠執行下一條命令同樣。因此單線程的缺點是:CPU利用率大幅下降

從圖中咱們能夠發現,CPU徹底能夠在任務1和任務2之間來回切換,使任務2沒必要等待10秒後再運行,系統的運行效率大大獲得提高。

注意一點!!!!!

多線程是異步的,千萬不要把IDEA裏代碼的順序當成線程執行的順序,線程被調用時隨機的。

1.2 使用多線程

1.2.1 繼承Thread類

在Java的JDK開發包中,已經自帶了對多線程的支持,實現多線程編程的方式主要有兩種:一種是繼承Thread類,一種是實現Runable接口。

建立多線程以前咱們先看看Thread的結構,以下:

public class Thread implements Runnable

從上面的源代碼中咱們能夠發現,Thread類實現了Runnable接口,他們之間具備多態關係。

其實,使用繼承Thread 類的方式實現多線程時,最大的侷限性就是不支持多繼承,由於在Java語言的特色就是單繼承,因此爲了實現多繼承徹底能夠採用實現Runnable接口的方式。總的來講,沒有什麼本質的區別。

首先咱們建立一個自定義的線程呢類 MyThread.java,繼承Thread類而且重寫run()方法,代碼以下:

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("MyThread");
    }
}

運行類代碼以下:

public class Test {
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        myThread.start();
        System.out.println("運行結束");
    }
}

運行結果以下:

運行結束
MyThread

從運行的結果來看, MyThread.java中的run方法執行的時間比較晚,這也說明在使用多線程技術時,代碼的運行結果與代碼執行順序或調用順序是無關的。

爲何會出現這樣的結果呢?是由於線程是一個子任務,CPU以不肯定的方式或是以隨機的時間來調用線程中的run方法,因此會出現先打印 「 運行結束 」,後輸出 「 MyThread」 這樣的結果。

上面咱們提出線程調用的隨機性,下面咱們建立MyThread.java 來演示線程的隨機性。

建立自定義線程類MyThread.java,代碼以下:

public class MyThread extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                int time = (int) Math.random() * 1000;
                Thread.sleep(time);
                System.out.println("run=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

再建立運行類Test.java,代碼以下:

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("myThread");
        myThread.start();
        try {
            for (int i = 0; i < 5; i++) {
                int time = (int) Math.random() * 1000;
                Thread.sleep(time);
                System.out.println("main=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

代碼運行結果:

main=main
run=myThread
main=main
run=myThread
main=main
run=myThread
main=main
run=myThread
main=main
run=myThread

在代碼中,爲了展現線程具備隨機性,因此使用隨機數的形式來使線程獲得掛起的效果,從而表現出CPU執行那個線程具備不肯定性。

說明:MyThread.java中的start()方法通知「線程規劃器」,此線程已經準別就緒,等待調用線程對象的run方法,這個過程其實就是讓CPU安排一個時間來調用MyThread.java類中的run方法,也就是使線程獲得運行。

在強調一點,執行start()方法的順序不表明線程啓動的順序,建立測試類說明一下,代碼以下:

public class MyThread extends Thread {
    private int i;

    public MyThread(int i) {
        this.i = i;
    }

    @Override
    public void run() {
        System.out.println("i=" + i);

    }
}

運行類Test.java,代碼以下:

public class Test {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread(1);
        MyThread myThread2 = new MyThread(2);
        MyThread myThread3 = new MyThread(3);
        MyThread myThread4 = new MyThread(4);
        MyThread myThread5 = new MyThread(5);
        myThread1.start();
        myThread2.start();
        myThread3.start();
        myThread4.start();
        myThread5.start();
    }

程序運行後的結果如圖:

i=1
i=2
i=5
i=3
i=4

1.2.2 實現Runnable接口

若是咱們建立的線程類已經有一個父類了,這時候就不能繼承Thread類了,由於在Java中不支持多繼承,因此咱們須要實現Runnable接口來實現多線程。

建立一個實現Runnable接口的類

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("運行中!!!!!");
    }
}

如何使用MyRunnable.java類呢,咱們看一下Thread.java類的構造函數,以下圖所示:

Thread.java類中的8個構造函數中,有兩個構造函數Thread(Runnable target)和Thread(Runnable target, String name)能夠傳遞Runnable接口,說明構造函數支持傳入一個Runnable接口的對象,運行類代碼以下:

public class Test {
    public static void main(String[] args) {
       MyRunnable myRunnable=new MyRunnable();
       Thread thread=new Thread(myRunnable);
       thread.start();
        System.out.println("運行結束!!!");
    }
}

運行結果如圖:

運行結束!!!
運行中!!!!!

另外須要說明一點,Thread.java類也是實現Runnable接口,以下:

public class Thread implements Runnable

那也就意味着構造函數Thread(Runnable target)不光能夠傳入Runnable接口對象,還能夠傳入一個Thread.java類的對象,這樣作徹底能夠將一個Thread.java對象的run()方法交給其餘的線程進行調用。

1.2.3 實例變量與線程安全

自定義線程類中的實例變量針對其餘線程能夠有共享和不共享之分,下面咱們分開來講明這兩點:

不共享數據的狀況

不共享數據的狀況以下圖展現說明:

下面咱們經過一個示例來看下數據不共享狀況,建立一個MyThread.java類代碼以下:

public class MyThread extends Thread {
    private int count = 5;

    public MyThread(String name) {
        this.setName(name);
    }

    @Override
    public void run() {
        while (count > 0) {
            count--;
            System.out.println("由 " + this.currentThread().getName() + " " + "計算, count " + count);
        }
    }
}

運行類Test.java代碼以下:

public class Test {
    public static void main(String[] args) {
        MyThread myThread=new MyThread("A");
        MyThread myThread1=new MyThread("B");
        MyThread myThread2=new MyThread("C");
        myThread.start();
        myThread1.start();
        myThread2.start();
    }
}

不共享數據運行結果以下:

由 B 計算, count 4
由 C 計算, count 4
由 A 計算, count 4
由 C 計算, count 3
由 B 計算, count 3
由 C 計算, count 2
由 A 計算, count 3
由 C 計算, count 1
由 B 計算, count 2
由 C 計算, count 0
由 A 計算, count 2
由 B 計算, count 1
由 A 計算, count 1
由 B 計算, count 0
由 A 計算, count 0

咱們總共建立了3個線程,每一個線程都有各自的count變量,本身減小本身的count變量的值,這樣的狀況就是變量不共享。

那麼,若是想實現3個線程共同對一個count變量進行減法操做的目的,該如何設計呢?

共享數據的狀況

共享數據的狀況以下圖:

共享數據的狀況就是多個線程同時訪問一個變量,好比實現投票功能的軟件時,多個線程能夠同時處理同一我的的票數。

下面咱們經過代碼實例演示一下共享數據的狀況,建立一個MyThread.java類代碼以下:

public class MyThread extends Thread {
    private int count = 3;

    @Override
    public void run() {
        count--;
        System.out.println("由 " + this.currentThread().getName() + " " + "計算, count " + count);

    }
}

運行類Test.java代碼以下:

public class Test {
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        Thread thread=new Thread(myThread,"A");
        Thread thread1=new Thread(myThread,"B");
        Thread thread2=new Thread(myThread,"C");
        thread.start();
        thread1.start();
        thread2.start();
    }
}

運行結果:

由 A 計算, count 0
由 C 計算, count 0
由 B 計算, count 0

從運行結果咱們能夠看出,線程A,線程B,線程C 的count值都是2,說明A,B,C同時對count進行處理,產生了「非線程安全」的問題。

那咱們修改代碼以下 即在run() 方法前面加上synchronized關鍵字:

public synchronized void run() {
    count--;
    System.out.println("由 " + this.currentThread().getName() + " " + "計算, count " + count);
}

從新運行程序就不回產生值同樣的狀況了,結果顯示以下:

由 A 計算, count 2
由 C 計算, count 1
由 B 計算, count 0

​ 經過在run()方法前面加上synchronized關鍵字,使多個線程在執行run()方法時,以排隊的形式進行處理。但一個線程調用run前,先判斷run方法有沒有被上鎖,若是上鎖,說明有其餘線程正在調用run方法,必須等其餘線程對run方法調用結束後才能夠執行run方法。這樣排隊調用run方法的目的,也就達到按順序對count變量減1的效果了。同時synchronized能夠在任何方法上加鎖,而加鎖的這段代碼叫作「互斥區」 或「臨界區」。

另外說明,當一個線程想要執行同步方法裏面的代碼時,線程首先去拿這把鎖,若是可以拿到這把鎖,那麼這個線程就能夠執行synchronized若是拿不到這把鎖,那麼線程就不斷的嘗試拿這把鎖,直到可以拿到爲止。

這裏咱們引出一個概念,「非線程安全」。非線程安全主要是指多個線程對同一個對象中的同一個實例變量進行操做時會出現值被更改,值不一樣步的狀況,進而影響程序的執行流程。

下面咱們來實現一個非線程安全的實例,loginServlet.java代碼以下:

public class loginServlet {
    private static String usernameRef;
    private static String passwordRef;

    public static void doPost(String username, String password) {
        try {
            usernameRef = username;
            if (username.equals("username=" + username)) {
                Thread.sleep(5000);
            }
            passwordRef = password;
            System.out.println("username=" + usernameRef + " " + " password=" + passwordRef);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

線程ALogin.java代碼以下:

public class ALogin extends Thread{
    @Override
    public void run() {
       loginServlet.doPost("a","aa");
    }
}

線程BLogin.java代碼以下:

public class BLogin extends Thread{
    @Override
    public void run() {
       loginServlet.doPost("b","bb");
    }
}

代碼實例運行結果:

username=a password=bb
username=b password=bb

由運行結果咱們能夠看出,出現了線程不安全的問題,解決這個問題的方法咱們使用synchronized關鍵字修飾別調用的方法便可。

public static synchronized void doPost(String username, String password)

1.3 多線程經常使用方法

1.3.1 CurrentThread() 方法

CurrentThread() 方法返回代碼段正在被哪一個線程調用的信息,下面經過一個示例來講明:

public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

運行結果:

main

結果說明,main 方法被名爲main的線程調用。

繼續實驗,建立 MyThread.java代碼以下:

public class MyThread extends Thread {
    public MyThread() {
        System.out.println("構造方法的打印:"+Thread.currentThread().getName());
        this.setName("MyThread");
    }

    @Override
    public void run() {
        System.out.println("run方法的打印:"+Thread.currentThread().getName());
    }
}

運行類Test.java代碼以下:

public class Test {
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        myThread.start();
    }
}

運行結果:

構造方法的打印:main
run方法的打印:MyThread

從運行結果咱們能夠發現,MyThread.java類的構造函數是被main線程調用的,而run方法是被名稱爲MyThread的線程調用的。

1.3.2 isAlive()方法

方法isAlive()的功能是判斷當前的線程是否處於活動狀態

建立 MyThread.java代碼以下:

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("run="+this.isAlive());
    }
}

運行類Test.java代碼以下:

public class Test {
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        System.out.println("begin==" + myThread.isAlive());
        myThread.start();
        // Thread.sleep(1000);
        System.out.println("end==" + myThread.isAlive());
    }
}

運行結果:

begin==false
end==true
run=true

方法isAlive()的做用是測試線程是否處於活動狀態,那什麼是活動狀態呢?活動狀態就是線程已經啓動且還沒有終止。即線程處於正在運行或準備開始運行的狀態,就認爲線程是存活的。

說明一下,以下代碼:

System.out.println("end==" + myThread.isAlive());

雖然上面打印的值是true,可是此值是不肯定的,打印true是由於MyThread線程還未執行完畢,因此輸出true,若是修改代碼把Test.java中 Thread.sleep(1000)代碼放開,運行結果輸出是false,由於MyThread線程在1秒內就執行完畢。

另外,在使用isAlive()方法時,若是將線程對象以構造函數的方式傳遞給Thread對象進行start()啓動時,運行的結果和前面的實例是有差別的,形成這樣差別的緣由是來自於Thread.currentThread()和this的差別。

1.3.3 sleep() 方法

方法sleep()的做用是在指定的毫秒數內讓當前「 正在執行的線程」休眠()暫停執行,這個正在執行的線程是指this.currentThread()返回的線程,前面的實例也提到過,這裏代碼不作說明。

1.3.4 getId() 方法

getId() 方法的做用是取得線程的惟一的惟一標示。

運行類Test.java代碼以下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+ ":"+ Thread.currentThread().getId());
    }
}

運行結果:

main: 1

1.3.5 yield()方法

yield()方法的做用是放棄當前的CPU資源,將它讓給其餘的任務去佔用CPU時間。可是放棄的時間是不肯定的,有可能剛剛放棄,立刻又得到CPU時間片。

建立 MyThread.java代碼以下:

public class MyThread extends Thread {

    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 1000000; i++) {
            //Thread.yield();
            count+=count;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用時:"+(endTime-beginTime)+ "毫秒!");
    }
}

運行類Test.java代碼以下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        myThread.start();
    }
}

程序運行後結果:

用時:3毫秒!

去掉註釋代碼,再次運行,結果以下:說明將CPU讓給其餘資源致使速度變慢。

用時:323毫秒!

1.4 中止線程

中止一個線程意味着在線程處理完任務以前停掉正在作的操做,也就是放棄當前的操做。

在Java中有一下三種方式能夠終止正在運行的線程:

  • 使用退出標示,使線程正常退出,也就是當run方法完成後線程終止。
  • 使用stop方法強行終止線程,由於stop和suspend及resume同樣,都是做廢的方法,使用起來起來kennel產生不可預料的結果。
  • 使用interrupt方法中斷線程(經常使用)

大多數中止一個線程的操做使用Thread.interrupt()方法,儘管方法的名稱是「 中止,停止」的意思,可是這個方法不會中止一個正在運行的線程,還須要加入一個判斷才能夠完成線程的中止操做。

1.4.1 中止不了的線程

使用interrupt()方法中止線程,可是interrupt()放大不像for+break語句那樣,能夠立刻就中止循環,調用interrupt()方法僅僅是在當前線程中打了一箇中止的標記,並非真正的中止線程。

建立 MyThread.java代碼以下:

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <= 10000; i++) {
            System.out.println("i="+i);
        }
    }
}

運行類Test.java代碼以下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        myThread.start();
        myThread.interrupt();
    }
}

部分運行結果:

i=9997
i=9998
i=9999
i=10000

從運行的結果來看,調用interrupt方法並無中止線程,那麼如何中止線程呢?

1.4.2 判斷線程是不是中止狀態

判斷線程的狀態是否是中止的,在Java的JDK中,Thread.java類中提供了兩種方法:

this.isInterrupted():測試當前線程是否已經中斷。
this.isInterrupted(); 測試線程是否已經中斷。

public static boolean interrupted()
public boolean isInterrupted()

一個是靜態的方法一個不是靜態的方法。

下面咱們說明如何是main線程產生中斷的效果呢? 建立Test.java類,代碼以下:

public class Test {
    public static void main(String[] args) throws InterruptedException {       
        Thread.currentThread().interrupt();
        System.out.println("是否中止1:"+Thread.interrupted());
        System.out.println("是否中止2:"+Thread.interrupted());
    }
}

程序運行後結果:

是否中止1:true
是否中止2:false

從運行的結果來看,interrupted方法判斷當前線程是不是中止狀態。可是爲何第二個布爾值爲false呢,這是因爲 interrupted方法有清除狀態的功能,因此第二次調用的返回值是false。而isInterrupted判斷線程是否中斷不清除狀態標示。

接下來咱們經過一個示例來講明如何中止一個線程,建立 MyThread.java代碼以下:

public class MyThread extends Thread {

    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted()) {
            count++;
        }
        System.out.println("循環次數:" + count + ",線程中斷,中斷信號:" + Thread.currentThread().isInterrupted());

    }
}

建立Test.java類,代碼以下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        myThread.start();
        Thread.sleep(5);
        myThread.interrupt();

    }
}

程序運行結果:

循環次數:88528,線程中斷,中斷信號:true

以上結果能夠看到循環 88528 次後,線程收到了中斷信號(即 Thread.currentThread().isInterrupted() 返回的結果爲 true),循環條件不知足條件,退出循環,執行完程序,自動中止線程,這種就屬於經過 interrupt 正確中止線程的狀況。

1.5 暫停線程

1.5.1 suspend與resume方法的使用

暫停線程意味着此線程還能夠恢復運行,在Java多線程中,可使用suspend方法暫停線程,使用resume方法恢復線程的執行。

建立 MyThread.java代碼以下:

public class MyThread extends Thread {
    private  long i=0;

    public long getI() {
        return i;
    }

    public void setI(long i) {
        this.i = i;
    }

    @Override
    public void run() {
      while (true){
          i++;
      }
    }
}

建立Test.java類,代碼以下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        myThread.start();
        Thread.sleep(1000);
        //A段
        myThread.suspend();
        System.out.println("A= "+System.currentTimeMillis()+ " i="+myThread.getI());
        Thread.sleep(1000);
        System.out.println("A= "+System.currentTimeMillis()+ " i="+myThread.getI());

        myThread.resume();
        Thread.sleep(1000);

        myThread.suspend();
        System.out.println("B= "+System.currentTimeMillis()+ " i="+myThread.getI());
        Thread.sleep(1000);
        System.out.println("B= "+System.currentTimeMillis()+ " i="+myThread.getI());
    }
}

程序運行結果:

A= 1618220660051 i=690427887
A= 1618220661056 i=690427887
B= 1618220662061 i=1410759540
B= 1618220663066 i=1410759540

從程序運行結果來看,線程的確是被暫停了,並且還能夠恢復成運行的狀態。

1.6 線程的優先級

在操做系統中,線程能夠劃分優先級,優先級較高的線程獲得CPU資源較多,也就是CPU優先執行優先級較高的線程對象中的任務。

設置線程優先級有助於「線程規劃器」,肯定下一次選擇哪一個線程來優先執行。

設置線程優先級使用setPriority() 方法,此方法在JDK的源代碼以下:

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

在Java中,線程優先級分爲 1~~10這10個等級,若是小於1或者大於10,則JDK拋出異常爲 throw new IllegalArgumentException()

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

1.6.1 優先級的繼承特性

在Java中,線程的優先級具備繼承性,好比A線程啓動B線程,則B線程的優先級和A線程是同樣的。

建立 MyThread1.java代碼以下:

public class MyThread1 extends Thread{
    @Override
    public void run() {
        System.out.println("MyThread1 run Priority="+this.getPriority());
        MyThread2 myThread2=new MyThread2();
        myThread2.start();
    }
}

建立 MyThread2.java代碼以下:

public class MyThread2 extends Thread{
    @Override
    public void run() {
        System.out.println("MyThread2 run Priority="+this.getPriority());
    }
}

建立Test.java類,代碼以下:

public class Test {
    public static void main(String[] args) {
        System.out.println(" main thread begin  priority="+Thread.currentThread().getPriority());
        //Thread.currentThread().setPriority(8);
        System.out.println(" main thread end priority="+Thread.currentThread().getPriority());
        MyThread1 myThread1=new MyThread1();
        myThread1.start();
    }
}

運行結果:

main thread begin priority=5
main thread end priority=5
MyThread1 run Priority=5
MyThread2 run Priority=5

將代碼 Thread.currentThread().setPriority(8) 的註釋去掉,再次運行Test.java文件,顯示以下:

main thread begin priority=5
main thread end priority=8
MyThread1 run Priority=8
MyThread2 run Priority=8

1.6.2 優先級具備規則性

雖然使用setPriority()方法能夠設置線程的優先級,但尚未看到設置優先級帶來的效果。

建立 MyThread1.java代碼以下:

public class MyThread1 extends Thread{
    @Override
    public void run() {
        long beginTimeMillis = System.currentTimeMillis();
        long addResult=0;
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 50000; j++) {
                Random random=new Random();
                random.nextInt();
                addResult=addResult+j;
            }
        }
        long endTimeMillis = System.currentTimeMillis();
        System.out.println( " @@@@@@@@@@@@@@@@@@  thread 1 use time =  "+ (endTimeMillis-beginTimeMillis) );
    }
}

建立 MyThread2.java代碼以下:

public class MyThread2 extends Thread{
    @Override
    public void run() {
        long beginTimeMillis = System.currentTimeMillis();
        long addResult=0;
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 50000; j++) {
                Random random=new Random();
                random.nextInt();
                addResult=addResult+j;
            }
        }
        long endTimeMillis = System.currentTimeMillis();
        System.out.println( " @@@@@@@@@  thread 2 use time =  "+ (endTimeMillis-beginTimeMillis) );
    }
}

建立Test.java類,代碼以下:

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i <5 ; i++) {
            MyThread1 myThread1=new MyThread1();
            myThread1.setPriority(10);
            myThread1.start();
            MyThread2 myThread2=new MyThread2();
            myThread2.setPriority(1);
            myThread2.start();
        }

    }
}

程序運行的結果:

@@@@@@@@@@@@@@@@@@ thread 1 use time = 412
@@@@@@@@@ thread 2 use time = 426
@@@@@@@@@@@@@@@@@@ thread 1 use time = 438
@@@@@@@@@ thread 2 use time = 449
@@@@@@@@@@@@@@@@@@ thread 1 use time = 449
@@@@@@@@@@@@@@@@@@ thread 1 use time = 455
@@@@@@@@@ thread 2 use time = 459
@@@@@@@@@ thread 2 use time = 461
@@@@@@@@@@@@@@@@@@ thread 1 use time = 462
@@@@@@@@@ thread 2 use time = 463

從運行的結果咱們發現,高優先級的線程老是大部分先執行完,但不表明優先級高的所有執行完。另外,不要覺得MyThread1線程先被main線程調用就先執行完,出現這樣的結果是由於MyThread1線程的優先級高。當線程優先級差距較大時,誰先執行完和代碼的調用順序無關。同時說明線程的優先級具備必定的規則性,也就是CPU儘可能將執行資源讓給優先級比較高的線程。

1.6.3 優先級具備隨機性

由於優先級具備隨機性,也就是優先級比較高的線程不必定每一次都先執行完。

咱們將上面Test.java類代碼myThread2.setPriority(5),運行代碼結果以下:

@@@@@@@@@@@@@@@@@@ thread 1 use time = 440
@@@@@@@@@@@@@@@@@@ thread 1 use time = 464
@@@@@@@@@@@@@@@@@@ thread 1 use time = 465
@@@@@@@@@ thread 2 use time = 466
@@@@@@@@@ thread 2 use time = 472
@@@@@@@@@ thread 2 use time = 473
@@@@@@@@@ thread 2 use time = 475
@@@@@@@@@@@@@@@@@@ thread 1 use time = 476
@@@@@@@@@ thread 2 use time = 476
@@@@@@@@@@@@@@@@@@ thread 1 use time = 476

那麼,咱們得出一個結論,不要把線程的優先級與運行結果的順序做爲衡量的標準,優先級較高的不必定每一次都先執行完。也就是說優先級與打印的順序無關,由於它們的關係具備不肯定性和隨機性。

1.7 守護線程

在Java線程中有兩種線程,一種是用戶線程,另外一種是守護線程。

守護線程是一種特殊的線程,它的特性有「陪伴」的含義,當進程中不存在非守護線程了。則守護線程自動銷燬。典型的守護線程就是垃圾回收線程

通俗的說:「守護線程」:任何守護線程都是整個JVM中非守護線程的「保姆」,只有當前JVM實例中存在任何一個非守護線程沒有結束,守護線程就在工做,只有當最後一個非守護線程結束時,守護線程才隨JVM一同結束工做。Daemon的做用是爲其餘線程的運行提供便利服務,守護線程最典型的應用就是GC(垃圾回收器)。

建立 MyThread.java代碼以下:

public class MyThread extends Thread {
    private  int i=0;
    @Override
    public void run() {
        try {
            while (true){
                i++;
                System.out.println("i= "+i);
                Thread.sleep(1000);
            }
        }catch(InterruptedException e){
            e.printStackTrace();
        }

    }
}

建立Test.java類,代碼以下:

public class Test {
    public static void main(String[] args) throws  InterruptedException{
       MyThread myThread=new MyThread();
       myThread.setDaemon(true);
       myThread.start();
       Thread.sleep(5000);
        System.out.println("我離開 myThread 對象也不打印了,也中止了");
    }
}

程序運行的結果:

i= 1
i= 2
i= 3
i= 4
i= 5
我離開 myThread 對象也不打印了,也中止了

看到這裏今天的分享就結束了,若是以爲這篇文章還不錯,來個分享、點贊、在看三連吧,讓更多的人也看到~

歡迎關注我的公衆號 「JavaClub」,按期爲你分享一些技術乾貨。

相關文章
相關標籤/搜索