1. 程式人生 > 其它 >Java多執行緒-01-執行緒的建立

Java多執行緒-01-執行緒的建立

目錄


一.執行緒簡介




二.執行緒建立(重點)


Thread類


package 執行緒建立.Thread;

public class Thread_Test01 {
    public static void main(String[] args) {
        //main執行緒,主執行緒

        //建立一個執行緒物件
        MyThread t = new MyThread();
        //呼叫start方法開啟執行緒
        t.start();

        for (int i = 0; i < 2000; i++) {
            System.out.println("當前遊戲場景執行中"+i);
        }

    }
}

//建立執行緒的方式一:繼承Thread類,重寫run方法,呼叫start開啟執行緒
class MyThread extends Thread{
    @Override
    public void run() {
        //run方法執行緒體
        for (int i = 0; i < 2000; i++) {
            System.out.println("後臺載入下一遊戲場景"+i);
        }
    }
}

主執行緒和子執行緒並行交替執行




練習:同時下載三張網路圖片

需要用到工具類庫:commons-io

下載方法參考部落格:(54條訊息) commons-io的下載和使用_唯有一片炙熱的部落格-CSDN部落格_commons-io

匯入到IDEA使用:

1.新建包lib

2.拷貝外部庫到lib

3.右鍵lib,點選“新增為庫...”選項


public class Thread_WebPictureDown_Test {
    //main,主執行緒
    public static void main(String[] args) {
        //建立三個執行緒
        //地址太長這裡省略了
        WebPictureDownLoad_Thread t1 = new WebPictureDownLoad_Thread("https:...", "1");
        WebPictureDownLoad_Thread t2 = new WebPictureDownLoad_Thread("https:...", "2");
        WebPictureDownLoad_Thread t3 = new WebPictureDownLoad_Thread("https:...", "3");

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

//網圖下載執行緒
class WebPictureDownLoad_Thread extends Thread{

    private String url;//網路圖片地址
    private String name;//儲存的檔名

    //構造器
    public WebPictureDownLoad_Thread(String url, String name){
        this.url = url;
        this.name = name;
    }

    //網圖下載執行緒的執行體
    @Override
    public void run() {
        DownLoader downLoader = new DownLoader();
        downLoader.downLoad(this.url, this.name);
        System.out.println(name+"下載完畢");
    }
}

//下載器
class DownLoader{
    //下載方法
    public void downLoad(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO異常,downLoad方法出現問題");
        }
    }
}

Runnable介面(推薦)

具體原理在靜態代理部分介紹


public class Runnable_Test {
    public static void main(String[] args) {
        //建立Runnable介面的實現類物件
        LoadScene Scene_city_of_tear = new LoadScene("City_Of_Tear");
        //建立執行緒物件,通過執行緒物件來開啟我們的執行緒 (代理)
        Thread thread = new Thread(Scene_city_of_tear);
        thread.start();

        for (int i = 0; i < 2000; i++) {
            System.out.println("當前遊戲場景執行情況:"+i);
        }
    }
}

//建立執行緒的方式二:實現Runnable介面,重寫run方法,執行執行緒需要丟入Runnable介面實現類,呼叫start方法
class LoadScene implements Runnable{

    private String levelName;

    public LoadScene(String levelName){
        this.levelName = levelName;
    }

    @Override
    public void run() {
        //run方法執行緒體
        for (int i = 0; i < 2000; i++) {
            System.out.println("後臺載入場景:"+this.levelName+"  載入情況:"+i);
        }
    }
}

小結:對比Thread類和Runnable介面


實現Runnable介面的好處

1.介面可以多繼承,更靈活方便

2.方便同一個物件被多個執行緒使用


初識併發問題

例子:買電影票

//《阿凡達2》就要上映了,電影院一共放出10張票,小明,小紅,小玉都想去看(並且想看很多次!,所以他們都要搶很多票)
public class BuyTicket {
    public static void main(String[] args) {
        Ticket_Selling_Service Buy_AFanDa = new Ticket_Selling_Service("阿凡達2");

        //第二個引數是執行緒的name,可用.getName()獲得
        new Thread(Buy_AFanDa, "小明").start();
        new Thread(Buy_AFanDa, "小紅").start();
        new Thread(Buy_AFanDa, "小玉").start();
    }
}

//售票服務
class Ticket_Selling_Service implements Runnable{
    //電影的名字
    private String movieName;
    //阿凡達電影票總數
    private int ticketNums_AFanDa = 10;

    public Ticket_Selling_Service(String movieName){
        this.movieName = movieName;
    }

    @Override
    public void run() {
        while(true){
            if(this.ticketNums_AFanDa <= 0){
                System.out.println("尊敬的使用者"+Thread.currentThread().getName()+"你好,《阿凡達2》電影票已售完");
                break;
            }

            //模擬延時(資料量小,cpu跑太快了,不便於測試)
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"搶到了"+this.movieName+"的第"+this.ticketNums_AFanDa-- +"張票");
        }
    }
}

發現問題:多個執行緒操作同一個資源的情況下,執行緒不安全,資料紊亂

在後面的執行緒同步部分解決這個問題


龜兔賽跑

//龜兔賽跑,兔子比烏龜跑快,兔子每隔10米睡一次覺
public class Race_Test {
    public static void main(String[] args) {
        Race race = new Race();

        new Thread(race, "兔子").start();
        new Thread(race, "烏龜").start();
    }
}

class Race implements Runnable{
    //賽道總長100米
    private static int TotalLength = 100;
    //獲勝者
    private static String winner = null;

    @Override
    public void run() {

        if(Thread.currentThread().getName().equals("兔子")){
            //模擬兔子跑步,一輪迴圈加25米
            for (int howFarHasRuned = 0; howFarHasRuned <= 100; howFarHasRuned = howFarHasRuned + 25){
                //模擬兔子睡覺,每10米睡1納秒
                if(howFarHasRuned % 10 == 0){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("兔子-->已經跑了"+howFarHasRuned+"米");
                //判斷比賽是否已經結束了
                if(GameOver(howFarHasRuned))      break;
            }
        }
        else{
            //模擬烏龜跑步,一輪迴圈加1米
            for (int howFarHasRuned = 0; howFarHasRuned <= 100; howFarHasRuned++) {
                System.out.println("烏龜-->已經跑了"+howFarHasRuned+"米");
                //判斷比賽是否已經結束了
                if(GameOver(howFarHasRuned))      break;
            }
        }
    }

    //判斷比賽是否結束
    private boolean GameOver(int howFarHasRuned){
        if(winner != null)      return true;//已經有獲勝者了
        else{
            if(howFarHasRuned >= 100){//獲勝者產生
                winner = Thread.currentThread().getName();
                System.out.println("獲勝者是-->"+winner);
            }
        }
        return false;
    }
}

Callable介面





靜態代理

結婚的例子

//靜態代理模式總節
//真實物件和代理物件都要實現同一個介面
//代理物件要代理真實角色

//好處
//代理物件可以做很多真實物件做不了的事情
//真實物件只需要專注於做自己的事情

//一下是我目前的理解:
//舉個現實生活中的例子
//個人遊戲開發者A是程式高手可是不會其他的
//那麼A可以把音樂代理給音樂創作者B,把美術代理給美術創作者C
//A自己只需要專注於把程式部分做好就可以了

//簡而言之:把專業的事情代理給專業的物件去做,每個物件專精於自己的事情

public class Marry_Test {
    public static void main(String[] args) {
        MarryPeople people_Alice = new MarryPeople("Alice");

        WeddingCompany weddingCompany = new WeddingCompany(people_Alice);
        weddingCompany.HappyMarry();
    }
}

//結婚介面
interface Marry{
    void HappyMarry();
}

//真實角色:結婚的人
class MarryPeople implements Marry{
    private String name;

    //構造器
    public MarryPeople(String name){
        this.name = name;
    }

    @Override
    public void HappyMarry() {
        System.out.println(this.name+"-->結婚");
    }
}

//代理角色:婚慶公司幫助別人結婚
class WeddingCompany implements Marry{
    //代理目標(實現了結婚介面的真實物件)
    private Marry target;

    //構造器
    public WeddingCompany(Marry target){
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();//真實物件
        after();
    }

    private void before() {
        System.out.println("---結婚前---");
    }

    private void after() {
        System.out.println("---結婚後---");
    }
}

靜態代理模式總節:
真實物件和代理物件都要實現同一個介面
代理物件要代理真實角色

好處:
代理物件可以做很多真實物件做不了的事情
真實物件只需要專注於做自己的事情

我目前的理解:
舉個現實生活中的例子
個人遊戲開發者A是程式高手可是不會其他的
那麼A可以把音樂代理給音樂創作者B,把美術代理給美術創作者C
A自己只需要專注於把程式部分做好就可以了

簡而言之:把專業的事情代理給專業的物件去做,每個物件專精於自己的事情


多執行緒中的靜態代理

第一行程式碼是多執行緒中的靜態代理

第二行程式碼是之前結婚的例子


Thread(代理物件)和藍色框裡的東西(真實物件)都實現了Runnnable介面

(這裡用到了Lamda表示式,之後會介紹)


猜測:

Thread呼叫start()方法後會呼叫自己的run()方法 (具體怎麼調的不太清楚)

在run()方法中呼叫真實物件的run()方法


Lamda表示式




推導Lamda表示式

使用前提:介面為函式式介面(介面只有一個方法且是抽象方法)

場景:名叫Apple的類只使用一次

逐步優化,把程式碼變簡單


1.外部類

public class Lamda_Test01 {
    public static void main(String[] args) {
        Food apple = new Apple();
        apple.lamda();
    }
}

//外部類
class Apple implements Food{
    @Override
    public void lamda() {
        System.out.println("吃了蘋果");
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void lamda();
}

2.靜態內部類

public class Lamda_Test02 {
    //靜態內部類
    static class Apple implements Food{
        @Override
        public void lamda() {
            System.out.println("吃了蘋果");
        }
    }

    public static void main(String[] args) {
        Food apple = new Apple();
        apple.lamda();
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void lamda();
}

3.區域性內部類

public class Lamda_Test03 {
    public static void main(String[] args) {
        //區域性內部類
        class Apple implements Food{
            @Override
            public void lamda() {
                System.out.println("吃了蘋果");
            }
        }
        Food apple = new Apple();
        apple.lamda();
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void lamda();
}

4.匿名內部類

public class Lamda_Test04 {
    public static void main(String[] args) {
        //匿名內部類
        Food apple = new Food() {//new了一個繼承Food介面但是沒有名稱的實現類
            @Override
            public void lamda() {
                System.out.println("吃了蘋果");
            }
        };
        apple.lamda();
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void lamda();
}

5.用lamda簡化

public class Lamda_Test05 {
    public static void main(String[] args) {
        //lamda
        Food apple = ()->{
          System.out.println("吃了蘋果");
        };
        apple.lamda();
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void lamda();
}

6.簡化大括號(當方法內只有一行語句時)

public class Lamda_Test05 {
    public static void main(String[] args) {
        //lamda
        Food apple = ()->System.out.println("吃了蘋果");
        apple.lamda();
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void lamda();
}

推導完畢


帶引數的lamda表示式

public class Lamda_Test06 {
    public static void main(String[] args) {
        //lamda
        Food apple = (int i)->System.out.println("吃了"+i+"個蘋果");
        apple.eat(3);
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void eat(int i);
}

1.簡化引數型別

public class Lamda_Test07 {
    public static void main(String[] args) {
        //lamda
        Food apple = (i)->System.out.println("吃了"+i+"個蘋果");
        apple.eat(3);
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void eat(int i);

2.簡化小括號(函式只有一個引數時)

public class Lamda_Test08 {
    public static void main(String[] args) {
        //lamda
        Food apple = i->System.out.println("吃了"+i+"個蘋果");
        apple.eat(3);
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void eat(int i);
}

兩個引數,有多行語句的例子

public class Lamda_Test09 {
    public static void main(String[] args) {
        //lamda
        Food apple = (name,i)->{
            System.out.println(name+"吃了"+i+"個蘋果");
            System.out.println(name+"吃飽了");
        };
        apple.eat("Alice", 7);
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void eat(String name, int i);
}

使用舉例

無引數

public class Lamda_Test {
    public static void main(String[] args) {
        //lamda
        Food apple = ()->System.out.println("吃了蘋果");
        apple.eat();
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void eat();
}

一個引數

public class Lamda_Test {
    public static void main(String[] args) {
        //lamda
        Food apple = i->System.out.println("吃了"+i+"個蘋果");
        apple.eat(7);
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void eat(int i);
}

多個引數,多行語句

public class Lamda_Test {
    public static void main(String[] args) {
        //lamda
        Food apple = (name,i)->{
            System.out.println(name);
            System.out.println("吃了"+i+"個蘋果");
        };
        apple.eat("Alice", 7);
    }
}

//定義函式式介面
interface Food{
    //隱式宣告為 public abstract
    void eat(String name, int i);
}