1. 程式人生 > 其它 >CEOI2020 星際迷航

CEOI2020 星際迷航

執行緒


要想學習多執行緒,就得先知道什麼是執行緒,要想知道執行緒,就得先知道什麼是程序

  • 程序:
    是指正在執行的程式,是系統進行資源分配和呼叫的獨立單位,每一個程序都有它自己的記憶體空間和資源。
    通過工作管理員看
  • 執行緒:
    是程序的單個順序控制流,或者就是說是一個單獨執行的路徑
    如果一個程序只有一條執行路徑,稱之為單執行緒
    如果一個程序有多條執行路徑,稱之為多執行緒
    執行緒是包含在程序中的。
    舉例:掃雷,360防毒軟體,百度網盤

何為序列,並行,併發?

  • 1、序列,是指一個程式中所有的任務都是按照先後順序執行的,在前一個任務還沒有處理完的情況下,是不會進行處理下一個任務的。
    舉例:理髮店只有一個理髮師,很多人去理髮,就需要排隊,就有先後順序,先等前面的人理完髮,再輪到後面的人。
  • 2、並行,是指將任務分給不同的處理器去畜欄裡,每一個處理器中的任務再進行序列處理
    舉例:火車站上有很多賣票視窗,多個視窗同時賣票,但是呢,針對於某一個視窗來說,是一個接著一個去處理的。
  • 3、併發,是指一個現象,併發需要處理器的支援,比如在處理一個任務的時候,作業系統可以呼叫資源去處理其他的任務,這個任務並行還是序列都可以
    無論是序列還是並行,都需要處理支援併發。
    舉例:假設喝水是一個任務,每個火車站售票員,他再售票的同時也能喝水,這就表示支援併發

思考?JVM啟動的時候是單執行緒還是多執行緒呢?多執行緒
  • JVM啟動的時候,相當於啟動了一個程式,就是啟動了一個程序
    其中包含了主執行緒,垃圾回收執行緒。
    在啟動JVM的時候,最低的要求是需要啟動兩個執行緒,所以JVM啟動的時候是多執行緒程式。

建立執行緒的第一種方式:繼承Thread類,重寫run方法

  • 1、建立一個自定義類繼承Thread類
  • 2、這個繼承的類要重寫run方法
    1)為什麼要重寫run方法?
    2)重寫run方法後如何使用?
  • 3、根據這個類建立執行緒物件
  • 4、啟動執行緒

面試題:執行緒呼叫start()和run()方法的區別?

  • 單純地呼叫run方法僅僅表示的是第一個物件呼叫普通的方法,所以這裡的執行還是按照自上而下順序執行,所以這裡依舊是單執行緒程式
    要想看到多執行緒程式的執行效果,就必須換一種方式啟動執行緒。start()
    run方法中僅僅是封裝了被執行緒執行的邏輯程式碼,但是直接物件呼叫run方法於普通的方法呼叫沒有任何區別
    start()方法呼叫,首先做的是啟動一個執行緒,然後再由JVM去呼叫該執行緒物件中的run()方法
模擬多執行緒環境
  • 要想模擬多執行緒環境,就得建立多個執行緒物件,然後同時啟動
    模擬多執行緒環境,至少建立2個及以上的執行緒物件

注意事項

  • 1、啟動執行緒呼叫的是start()方法
  • 2、執行緒的呼叫start()方法先後順序與今後真正執行的順序沒有影響

執行緒的邏輯程式碼

package com.shujia.wyh.day25;
public class MyThread1 extends Thread{
    @Override
    public void run() {
        //寫的是執行緒要執行的邏輯程式碼
//        System.out.println("數加真好!");
        //一般情況下,執行緒執行的邏輯程式碼都是比較耗時並且複雜的,為了模擬這裡耗時複雜,我使用迴圈列印來代替
        for(int i=1;i<=300;i++){
            System.out.println(i);
        }
    }
}

Thread類的基本獲取和設定方法

1.設定名字(兩種方法)

  • 1、public final void setName(String name)
  • 2、通過構造方法給執行緒起名字Thread(String name)

獲取名字

  • 如何獲取一個執行緒的名字呢?
    public final String getName()
public class MyThreadDemo3 {
    public static void main(String[] args) {
//        MyThread2 t1 = new MyThread2(); //Thread-0
//        MyThread2 t2 = new MyThread2(); //Thread-1
        MyThread2 t1 = new MyThread2("李毅");
        MyThread2 t2 = new MyThread2("小虎");

//        t1.setName("李毅");
//        t2.setName("小虎");

        //啟動執行緒
        t1.start();
        t2.start();
    }
}

執行緒排程問題

問題引入

  • 假如我們的計算機只有一個 CPU,那麼 CPU 在某一個時刻只能執行一條指令,執行緒只有得到 CPU時間片,也就是使用權,
    才可以執行指令。那麼Java是如何對執行緒進行呼叫的呢?

執行緒的兩種排程模型(Java屬於搶佔式排程模型)

  • 1、分時排程模型 所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間片
  • 2、搶佔式排程模型 優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個,

Java使用的是搶佔式排程模型。***

獲取執行緒中的優先順序方法:

  •     public final int getPriority() 返回此執行緒的優先順序。
    

設定執行緒優先順序的方法:

  • public final void setPriority(int newPriority) 更改此執行緒的優先順序。

最大和最小優先順序

public final static int MIN_PRIORITY = 1; 執行緒可以擁有的最小的優先順序
public final static int MAX_PRIORITY = 10; 執行緒可以擁有的最大的優先順序

注意事項:

  • 1、執行緒的預設優先順序是5
  • 2、設定優先順序的時候,範圍是1-10
  • 3、執行緒的優先順序越高僅僅表示的是獲取CPU時間片的機率會高一些,並不能保證一定會先執行。

程式碼如下:

public class ThreadPriorityDemo {
    public static void main(String[] args) {
        MyPriorityThread p1 = new MyPriorityThread();
        MyPriorityThread p2 = new MyPriorityThread();
        MyPriorityThread p3 = new MyPriorityThread();

        //獲取執行緒優先順序
//        int py1 = p1.getPriority();
//        System.out.println(py1);        5
//        int py2 = p2.getPriority();
//        System.out.println(py2);        5
//        int py3 = p3.getPriority();
//        System.out.println(py3);        5

        //可以設定執行緒的優先順序
        p1.setPriority(10);
        p2.setPriority(5);
        p3.setPriority(1);

        //設定各個執行緒的名字
        p1.setName("小花");
        p2.setName("小黑");
        p3.setName("小白");

        p1.start();
        p2.start();
        p3.start();
    }
}

執行緒加入 public final void join()

  • public final void join()
    執行緒物件呼叫該方法的時候,目的是讓呼叫該方法的當前執行緒先執行完,執行完畢後,再讓其他執行緒執行
    其他沒有呼叫join方法的執行緒,他們之間還是會搶CPU執行權的。

注意事項:

  • join方法的呼叫,必須是緊跟著當前執行緒start()方法後呼叫,否則不起作用。
public class JoinDemo {
    public static void main(String[] args) {
        MyJoinThread jd1 = new MyJoinThread();
        MyJoinThread jd2 = new MyJoinThread();
        MyJoinThread jd3 = new MyJoinThread();

        //給各個執行緒取名字
        jd1.setName("無敵小可愛");
        jd2.setName("無敵菠蘿怪");
        jd3.setName("無敵香蕉π");

        //注意:join 方法的呼叫,必須是緊跟著當前執行緒start()方法後呼叫的,否則不起作用;
        //啟動各個執行緒
//        try {
//            jd1.join();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        jd1.start();
                try {
            jd1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        jd2.start();
        jd3.start();
    }
}

執行緒禮讓 public static void yield()

  • 禮讓執行緒的目的是暫停當前正在執行的執行緒,並讓其他執行緒執行,
    它的作用實際上是為了讓執行緒之間看起來更加和諧,它並不能保證多個執行緒之間一人一次。
public class MyYieldDemo {
    public static void main(String[] args) {
        //建立多執行緒環境
        MyYieldThread t1 = new MyYieldThread();
        MyYieldThread t2 = new MyYieldThread();
        MyYieldThread t3 = new MyYieldThread();

        //給執行緒設定名字
        t1.setName("小白");
        t2.setName("小黑");
        t3.setName("小紅");

        t1.start();
        t2.start();
        t3.start();
    }
}

後臺執行緒(守護執行緒) public final void setDaemon(boolean on)

執行緒分類

  • 使用者執行緒:我們在學習執行緒之前,執行起來的一個一個程式中的執行緒都是使用者執行緒
  • 守護執行緒:所謂的守護執行緒,指的是程式執行的時候,在後臺提供了一個通用的服務執行緒,比如說垃圾回收執行緒,他就是一個守護執行緒。
    這種執行緒不一定是要存在的,但是可能程式會出問題。只要程式存在使用者執行緒,程式就不會停止
守護執行緒的設定:
  • public final void setDaemon(boolean on)

注意事項:

  • 1、守護執行緒必須在啟動之前進行設定
public class ThreadDaemonDemo {
    public static void main(String[] args) {
        //模擬多執行緒環境
        MyDaemonThread dd1 = new MyDaemonThread();
        MyDaemonThread dd2 = new MyDaemonThread();
        MyDaemonThread dd3 = new MyDaemonThread();

        //給執行緒付名字
//        dd1.setName("草莓");
//       dd1.setDaemon(true);
        dd2.setName("西瓜");
        dd2.setDaemon(true);
        dd3.setName("菠蘿");
        dd3.setDaemon(true);

        //當所有執行緒都為守護程序時。守護程序會馬上死亡,因為它沒有需要守護的東西

        //當草莓為執行緒,其他倆是守護執行緒時,如果草莓程序結束,那麼,西瓜和菠蘿也會很快調亡

        //啟動執行緒
//        dd1.start();
        dd2.start();
        dd3.start();
    }
}

休眠執行緒

  • 模擬真實環境,讓執行緒進入休眠狀態
package com.bigdat.java.day26;

public class MySleepThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <= 300; i++) {
            //需求:每列印一次,間隔 1000 毫秒在列印下一個
            //public static void sleep(long millis) throws InterruptedException
            //史當前正在執行的執行緒已指定的毫秒數暫停
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+":"+i);
        }
    }
}
package com.bigdat.java.day26;
/*
    public static void sleep(long millis)
 */
public class ThreadSleepDemo {
    public static void main(String[] args) {
        //建立三個程序
        MySleepThread st1 = new MySleepThread();
        MySleepThread st2 = new MySleepThread();
        MySleepThread st3 = new MySleepThread();

        //給程序名字
        st1.setName("視窗一");
        st2.setName("視窗二");
        st3.setName("視窗三");

        //啟動執行緒
        st1.start();
        st2.start();
        st3.start();

    }
}

中斷執行緒

  • 在自定義類中定義每個執行緒睡眠5秒
public class MyStopTread extends Thread{
    @Override
    public void run() {
        System.out.println("我開始睡覺。。。。");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("睡醒了。。。");
    }
}
  • 然後再測試類中,將正在睡眠的執行緒喚醒,即打斷執行緒的休眠時間

兩種方

  • public final void stop()
    強制打斷睡眠,程式停止,這個方法不太好,這個方法已經被棄用
  • public void interrupt()/打斷睡眠,提示打斷睡眠的錯誤,run方法中後面的程式碼繼續執行,直到執行完畢
public class ThreadStopDemo {
    public static void main(String[] args) {
        MyStopTread myStopTread = new MyStopTread();
        myStopTread.start();

        try {
            Thread.sleep(2000);
//            myStopTread.stop(); //強制打斷睡眠,程式停止,這個方法不太好,這個方法已經被棄用
            myStopTread.interrupt(); //打斷睡眠,提示打斷睡眠的錯誤,run方法中後面的程式碼繼續執行,直到執行完畢。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

多執行緒的第二種開啟方式-runnable

多執行緒的實現方式2:實現Runnable介面,實現run方

  • 1、自定義一個類實現Runnable介面
  • 2、實現run方法
  • 3、建立實現Runnable介面類對應的物件
  • 4、藉助Thread類建立執行緒物件,將自定義類作為構造方法的引數傳入

主方法程式碼:

public class MyRunnableDemo1 {
    public static void main(String[] args) {
        //建立實現Runnable介面類對應的物件
        MyRunnable1 myRunnable1 = new MyRunnable1();

        //建立多個執行緒物件
//        Thread t1 = new Thread(myRunnable1);
//        Thread t2 = new Thread(myRunnable1);

        //Thread(Runnable target, String name)
        //分配一個新的 Thread物件。
        Thread t1 = new Thread(myRunnable1,"小白");
        Thread t2 = new Thread(myRunnable1,"小黑");

        //給執行緒起名字
//        t1.setName("李毅");
//        t2.setName("小虎");

        //啟動執行緒
        t1.start();
        t2.start();
    }
}

自定義類程式碼

public class MyRunnable1 implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 300; i++) {
            //由於Runnable介面沒有getName()方法,所以在這裡無法直接呼叫獲取執行緒的名字
            //間接呼叫,
            //Thread類中有一個靜態的方法
            // public static Thread currentThread()返回對當前正在執行的執行緒物件的引用。
//            System.out.println(getName() + ":" + i);
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}