這篇 Java 多執行緒,你一定能看懂學會!

白翼快鬥發表於2020-10-13

前幾天學習了 Java 多執行緒,作為一個懶癌後期的患者,一直拖到現在才把所學的記錄下來,也算是複習了一遍 ?。希望大家多多支援喔!

在學習執行緒之前,我們先來了解一下程式吧!

程式

概述:正在執行的程式就是程式。程式是系統進行資源分配和呼叫的獨立單位,每一個程式都有它自己的記憶體空間和系統資源。
通過工作管理員,我們可以看到我們電腦現在的程式有哪些:
程式
多程式的意義:計算機可以在一個時間段內同時執行多個任務,可以提高CPU的使用率。
思考:我們一邊聽音樂(網易雲程式),一邊寫程式碼(IDEA程式),這兩個任務是同時的嗎?
對於多核CPU 它有可能是同時的,但是對於單核CPU來說,它在某一個時間點,它只能做一件事情。
但是我們在聽音樂的時候,同時在寫程式碼,我們感官上,這兩個任務是同時進行的。但是實際CPU在執行程式的時候進行了程式間的高速切換,這個切換時間非常的短,所以我們就感覺兩個程式是在同時進行的。

執行緒

在同一個程式中,可以同時執行多個任務。而這每一個任務,就是一個執行緒。

執行緒:是程式的執行單元,也是執行路徑。執行緒是程式使用CPU資源的最基本單位。
單執行緒:只有一個執行單元或只有一條執行路徑
多執行緒:有多個執行單元或多個執行路徑

例如:我們平時寫的這些程式使單執行緒的

public class Test {
    public static void main(String[] args) {
        System.out.println("程式碼塊1");
        method();
        System.out.println("程式碼塊2");
    }
    public static void method() {
        System.out.println("程式碼塊3");
        function1();
        function2();
        System.out.println("程式碼塊4");
    }

    private static void function1() {
    }
    private static void function2() {
    }
}

執行緒

多執行緒的意義:

  1. 執行緒的執行是搶佔式的。每一個執行緒都要去搶佔CPU資源(CPU執行權)。一個多執行緒的程式在執行時,如果一些執行緒必須等待的時候,CPU就會將資源提供給其他執行緒去使用這些資源。這樣的話就提高了CPU的使用率。
  2. 對於程式來說,如果它是多執行緒的,在搶佔CPU資源時,就有更大的機率搶佔到CPU資源。提高該程式使用率。

多執行緒

實現方式一:
繼承 Thread 類,重寫 run() 方法。
步驟:
1 自定義 MyThread 類,繼承 Thread 類
2 重寫 run() 方法
3 建立 MyThread 物件
4 啟動執行緒

public class MyThread extends Thread {
	// 重寫 run() 方法
    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            System.out.println(i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
    	// 建立 MyThread 物件
        MyThread myThread = new MyThread();
        // 啟動執行緒
        myThread.start();
    }
}

注:run() 和 start() 的區別是什麼?
run():僅僅封裝了執行緒所執行的程式碼,直接呼叫和普通方法沒有區別。
start():首先啟動執行緒,然後由 JVM 呼叫該執行緒的 run() 方法。

  1. 獲取執行緒名稱:
    public final String getName():返回此執行緒的名稱。
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            System.out.println(getName() + i);
        }
    }
}
  1. 設定執行緒名稱:
    法一:
    public final void setName(String name):將此執行緒的名稱更改為引數 name
myThread.setName("執行緒一");

法二:構造方法

public class MyThread extends Thread {
    public MyThread() {
    }
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            System.out.println(getName() + i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        // 使用有參構造:
        MyThread myThread = new MyThread("執行緒一:");
        myThread.start();

        MyThread myThread1 = new MyThread("執行緒二:");
        myThread1.start();
    }
}
  1. 獲取當前正在執行的執行緒名稱:
    public static Thread currentThread():返回對當前正在執行的執行緒物件的引用
String name = Thread.currentThread().getName();
System.out.println(name);
  1. 執行緒優先順序:
    執行緒有兩種排程模型:
    1、分時排程模型:所有的執行緒輪流使用 CPU,平均分配每個執行緒佔用 CPU 的時間段。
    2、搶佔式排程模型:Java 是搶佔式排程模型,會優先讓優先順序高的執行;優先順序相同的執行緒,隨機執行一個。(注:優先順序高只代表它搶到 CPU 的概率較大,不一定必須是先執行的)

獲取優先順序的方法:
public final int getPriority():返回此執行緒的優先順序
設定優先順序的方法:
public final int setPriority(int newPriority):設定此執行緒的優先順序

// 設定執行緒優先順序。
myThread1.setPriority(10);
myThread3.setPriority(1);

// 獲取執行緒優先順序。
System.out.println(myThread1.getPriority());
System.out.println(myThread2.getPriority());
System.out.println(myThread3.getPriority());
/*
輸出的結果:
	10
	5
	1
*/

注:1. 預設優先順序是5
2. 優先順序的取值範圍是 1-10

執行緒控制

  1. sleep():執行緒睡眠
    public static void sleep(long miles):導致當前正在執行的執行緒休眠(暫時停止執行)指定的毫秒數
public class MyThread3 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("有異常!");
            }
            System.out.println(getName() + "-" + i);
        }
    }
}
public class MyThreadDemo3 {
    public static void main(String[] args) {
        MyThread3 myThread1 = new MyThread3();
        MyThread3 myThread2 = new MyThread3();
        MyThread3 myThread3 = new MyThread3();

        myThread1.setName("喜羊羊");
        myThread2.setName("美羊羊");
        myThread3.setName("灰太狼");

        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}

執行上面的程式,可以看到每個執行緒都是輸出一次之後等待一秒再繼續輸入

  1. interrupt():執行緒中斷
    public void interrupt():中斷執行緒
public class MyThread7 extends Thread {
    @Override
    public void run() {
        System.out.println("執行緒開始執行" + new Date());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            System.out.println("程式出現錯誤!");
        }
        System.out.println("執行緒結束執行" + new Date());
    }
}
public class MyThreadDemo7 {
    public static void main(String[] args) {
        MyThread7 myThread1 = new MyThread7();

        //啟動執行緒
        myThread1.start();

        // myThread1 休眠時間超過三秒,就終止它。
        try {
            // 主執行緒休眠三秒。
            Thread.sleep(3000);
            myThread1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

執行緒中斷

  1. join():執行緒加入
    public void join():等待該執行緒終止
public class MyThread4 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println(getName() + ": " + i);
        }
    }
}
public class MyThreadDemo4 {
    public static void main(String[] args) {
        // 建立執行緒類物件
        MyThread4 myThread1 = new MyThread4();
        MyThread4 myThread2 = new MyThread4();
        MyThread4 myThread3 = new MyThread4();

        // 設定名稱。
        myThread1.setName("執行緒一");
        myThread2.setName("執行緒二");
        myThread3.setName("執行緒三");

        myThread1.start();
        // 加入執行緒。
        try {
            myThread1.join();
        } catch (InterruptedException e) {
            System.out.println("出錯了!");
        }
        myThread2.start();
        myThread3.start();
    }
}

執行上面程式,執行緒一執行完畢後,後面兩個執行緒才開始搶佔資源,進行執行

  1. yield():執行緒禮讓
    public static void yield():暫停當前正在執行的執行緒物件,並執行其他執行緒(可以減小搶佔競爭)
public class MyThread5 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println(getName() + ": " + i);
            // 執行緒禮讓
            Thread.yield();
        }
    }
}
public class MyThreadDemo05 {
    public static void main(String[] args) {
        // 建立執行緒類物件
        MyThread5 myThread1 = new MyThread5();
        MyThread5 myThread2 = new MyThread5();

        // 設定名稱。
        myThread1.setName("Andy");
        myThread2.setName("Jay");

        myThread1.start();
        myThread2.start();
    }
}
  1. setDeman():執行緒守護
    public final void setDaemon(boolean on):將該執行緒標記為守護執行緒或使用者執行緒(當正在執行的執行緒都是守護執行緒時,Java 虛擬機器退出,程式結束)
public class MyThread6 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println(getName() + ": " + i);
        }
    }
}
public class MyThreadDemo6 {
    public static void main(String[] args) {
        MyThread6 myThread1 = new MyThread6();
        MyThread6 myThread2 = new MyThread6();

        myThread1.setName("執行緒一");
        myThread2.setName("執行緒二");

        // 守護執行緒設定為true。主基地結束,其餘兩個執行緒一會也會結束。
        myThread1.setDaemon(true);
        myThread2.setDaemon(true);

        myThread1.start();
        myThread2.start();

        // 獲取主執行緒。
        Thread.currentThread().setName("主基地:");
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

執行緒的生命週期:
生命週期

實現方式二:
實現 Runnable 介面,重寫 run() 方法。然後可以分配類的例項,在建立 Thread 時作為引數傳遞。
步驟:
1.建立自定義執行緒類。
2.重寫 run 方法
3.建立自定義執行緒類物件
4.建立多個Thread類物件,將自定義執行緒類物件作為引數傳遞。
5.啟動執行緒。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
        	// getName() 是Thread的方法,所以在這裡應該這樣獲取執行緒名稱
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
public class MyRunnableDemo {
    public static void main(String[] args) {
    	// 建立自定義執行緒類物件
        MyRunnable mr = new MyRunnable();
		
		// 建立多個Thread類物件,將自定義執行緒類物件作為引數傳遞
        Thread t1 = new Thread(mr, "執行緒一");
        Thread t2 = new Thread(mr, "執行緒二");

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

注:為什麼建立執行緒的第一種方法還要有第二種方法?

  1. 繼承只能單繼承,如果自定義執行緒類有父類,則它不能再繼承Thread
  2. 第二種方式適合多個執行緒操作同一個資源這種情況,比較簡潔。把執行緒和程式程式碼 資料進行有效分離,較好地體現了物件導向的思想。

案例:共有150張票,建立三個執行緒,模擬電影院三個視窗的賣票情況。

public class MyRunnable implements Runnable {
	// 如果這裡是繼承 Thread 類,則需要用static來修飾票數,以保證三個執行緒共享同一個資源,三個視窗共賣這150張票。
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                try {
                	// 根據現實情況,賣票會出現延遲
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在售賣第" + (ticket--) + "張票。");
            }
        }
    }
}

執行上面程式,會發現有一票多賣和負數票的情況,該問題與執行緒安全有關。

執行緒安全:

出現原因:

  1. 多執行緒環境
  2. 存在共享資料
  3. 存在多條語句操作該共享資料

為了解決這個問題可以將操作共享資料的這段程式碼包裹起來,在有執行緒訪問這段程式碼時,其他執行緒不能訪問。

同步程式碼塊:
synchronize 關鍵字:

synchronize(物件名){
​		多條語句;
}

同步程式碼塊的物件是任意物件。
如果給這些執行緒傳遞同一個物件,就是相當於給了一個門,一把鎖;如果傳遞不同的物件,就相當於有多個門,多把鎖。

public class MyRunnable implements Runnable {
    private int tickets = 50;
    private Object object = new Object();
    @Override
    public void run() {

        while (true) {
            synchronized (object) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "賣出去標號為 " + (tickets--) + "的這張票");
                }
            }
        }
    }
}
public class MyRunnableDemo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr, "視窗一:");
        Thread t2 = new Thread(mr, "視窗二:");
        Thread t3 = new Thread(mr, "視窗三:");

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

注:如果使用繼承 Thread 類的方法,則需要將票數和鎖物件宣告為靜態的,以保證為所有物件共用。

private static int ticket = 50;
private static Object obj = new Object();

執行以上程式碼,上面的一票多賣和負數票的問題都被解決了。

同步程式碼塊的優缺點:
優點:解決了執行緒安全問題。
缺點:每個執行緒在執行前都要去判斷鎖物件,無形中增加了電腦負擔。

同步方法:
格式一:synchronized 許可權修飾符 返回值型別 方法名()
格式二:許可權修飾符 synchronized 返回值型別 方法名()

public class SaleTicket implements Runnable {
    private int ticket = 50;
    @Override
    public void run() {
        ticket();
    }

    private synchronized void ticket() {
        while (true) {
            if (ticket > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在售賣第" + (ticket--) + "張票");
            }
        }
    }
}
public class SaleTicketDemo {
    public static void main(String[] args) {
        SaleTicket st = new SaleTicket();

        Thread t1 = new Thread(st, "視窗一:");
        Thread t2 = new Thread(st, "視窗一:");
        Thread t3 = new Thread(st, "視窗一:");

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

同步方法的鎖物件是this,而如果該同步方法是靜態的,由於靜態方法隨著類的載入而載入,那麼它的鎖物件是該類的位元組碼檔案(類名.class)。

Lock鎖
Lock:這是一個介面,實現了比 synchronize 更語句和方法更廣泛的操作。
實現子類:ReentrantLock
成員方法:void lock() 上鎖;void unlock 解鎖。

public class SaleTicket implements Runnable {
    private int tickets = 50;
    // 使用多型的方法建立鎖物件
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 上鎖。
            lock.lock();
            if (tickets > 0) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "賣出去標號為 " + (tickets--) + "的這張票");
            }
            // 解鎖
            lock.unlock();
        }
    }
}
public class SaleTicketDemo {
    public static void main(String[] args) {
        SaleTicket s = new SaleTicket();

        Thread thread1 = new Thread(s, "視窗一:");
        Thread thread2 = new Thread(s, "視窗二:");
        Thread thread3 = new Thread(s, "視窗三:");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

注:上鎖和解鎖的位置和同步程式碼塊的位置相同。

死鎖:
同步程式碼塊的弊端:效率低,而且如果出現了同步巢狀,就容易產生死鎖的問題。
死鎖:是指兩個或兩個以上的執行緒在執行的過程中,因爭奪資源產生的一種互相等待的現象。

public class deadLock extends Thread {
    private boolean flag;
    private static Object lockA = new Object();
    private static Object lockB = new Object();

    // 構造方法
    public deadLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (lockA) {
                System.out.println("if lockA");
                synchronized (lockB) {
                    System.out.println("if lockB");
                }
            }
        } else {
            synchronized (lockB) {
                System.out.println("else lockB");
                synchronized (lockA) {
                    System.out.println("else lockA");
                }
            }
        }
    }
}
public class deadLockDemo {
    public static void main(String[] args) {
        deadLock deadLock1 = new deadLock(true);
        deadLock deadLock2 = new deadLock(false);

        deadLock1.start();
        deadLock2.start();
    }
}

生產者消費者模型
執行緒間通訊:不同種類的執行緒針對同一個資源進行操作

例如:不同執行緒操作同一個學生物件,一個執行緒用來設定學生物件,一個執行緒用來獲取學生物件。

public class Student {
    private String name;
    private int age;
	// 無參構造
	// 有參構造
	// get,set 方法
	// 此處省略
}
public class SetThread implements Runnable {
    private Student student;
    private int x = 0;

    public SetThread(Student student) {
        this.student = student;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (student) {
                if (x % 2 == 0) {
                    student.setName("William");
                    student.setAge(30);
                } else {
                    student.setName("Andy");
                    student.setAge(5);
                }
                x++;
            }
        }
    }
}
public class GetThread implements Runnable {
    private Student student;

    public GetThread(Student student) {
        this.student = student;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (student) {
                System.out.println(student.getName() + "---" + student.getAge());
            }
        }
    }
}
public class StudentDemo {
    public static void main(String[] args) {
        // 建立學生物件
        Student student = new Student();
        // 建立自定義執行緒類物件
        SetThread setThread = new SetThread(student);
        GetThread getThread = new GetThread(student);
        // 建立Thread類物件,將自定義執行緒類物件作為引數傳遞
        Thread thread = new Thread(setThread,"執行緒一");
        Thread thread2 = new Thread(getThread,"執行緒二");
        // 啟動執行緒
        thread.start();
        thread2.start();
    }
}

生產者消費者模型執行緒安全問題
出現問題:
a. 相同資料出現多次()
b. 姓名和年齡不匹配(CPU執行的原子性和執行緒的隨機性導致)

等待喚醒機制

問題:
第一次執行的時候,如果消費者先搶到 CPU 執行權,它就會去消費資料,但是此時的資料是預設值,沒有任何意義,所以應該等待生產者生產完資料之後,再去消費。
如果生產者搶到 CPU 執行權,它就會生產資料,但是,如果下一次都是生產者搶到 CPU 執行權,它就會重複生產資料,這樣是不合理的。應該等待消費者消費掉資料之後,再繼續生產。

解決思路:
生產者:先看是否有資料,如果有,就等待,如果沒有就生產資料。生產完畢之後通知消費者前來消費。
消費者:先看是否有資料,如果沒有,就等待,如果有,就消費。消費完之後通知生產者生產資料。
wait():執行緒等待
notify():喚醒等待的執行緒

執行緒狀態轉換及執行流程

執行緒組
當專案中有許多執行緒需要設定一些相同的屬性,比如都設定成守護執行緒。我們可以考慮根據執行緒的功能或者用途進行分組,然後針對組進行統一管理,這樣的好處是方便分類操作和管理。
預設分組:

public static void method1() {
	MyThreadGroup tg = new MyThreadGroup();

	Thread thread1 = new Thread(tg);
	Thread thread2 = new Thread(tg);

    System.out.println(thread1.getThreadGroup().getName());
    System.out.println(thread2.getThreadGroup().getName());
	System.out.println(Thread.currentThread().getThreadGroup().getName());
}

設定分組:

public static void method2() {
	ThreadGroup threadGroup = new ThreadGroup("守護執行緒");

	MyThreadGroup tg = new MyThreadGroup();

	Thread thread1 = new Thread(threadGroup, tg);
	Thread thread2 = new Thread(threadGroup, tg);

	System.out.println(thread1.getThreadGroup().getName());
	System.out.println(thread2.getThreadGroup().getName());
	System.out.println(Thread.currentThread().getThreadGroup().getName());

	// 將這個組設定成守護執行緒
	threadGroup.setDaemon(true);
}

執行緒池
在建立執行緒時,成本是比較高的,因為每一次建立執行緒時,都要與作業系統互動。而執行緒池會在程式啟動時,提前建立一些執行緒放線上程池中等待使用,這樣可以大大的提高執行效率。
特點:執行緒執行完畢後,不會死亡,而是重新回到執行緒池中,成為空閒狀態等下下一個執行緒使用。
JDK5 之前需要手動配置執行緒池,JDK5 之後Java開始內建執行緒池。
Executors:工廠類
通過下面的方法獲得執行緒池物件:
1、
2、public static ExecutorService newFixedThreadPool(int nThreads):建立一個可重用固定執行緒數的執行緒池
3、public static ExecutorService newSingleThreadExecutor():建立一個使用單個 worker 執行緒的 Executor
這些方法的返回值是 ExecutorService 物件,該物件表示一個執行緒池,它可以執行 Runnable物件 或者 Callable物件 物件代表的執行緒池。

操作步驟:
1、建立執行緒池物件
2、建立自定義類物件
3、提交 MyRunnable 到執行緒池
4、關閉執行緒池

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 1 建立執行緒池物件
        ExecutorService es = Executors.newFixedThreadPool(2);
        // 2 建立自定義執行緒類的物件
        MyThreadPool mtp = new MyThreadPool();
        // 3 提交Runnable例項
        es.submit(mtp);
        es.submit(mtp);
        // 4 關閉執行緒池
        es.shutdown();
    }
}

多執行緒第三種實現方式: Callable
Callable:這是一個介面,類似於 Runnable 介面,但是,Callable有返回值,並且可以丟擲異常。
所以,如果某些執行緒執行完畢後需要給我們返回一個執行結果時,我們可以使用Callable介面這些方式來實現多執行緒。

public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 30; i++) {
            System.out.println(i);
        }
        return null;
    }
}
public class MyCallableDemo {
    public static void main(String[] args) {
        // 1 建立執行緒池物件
        ExecutorService es = Executors.newFixedThreadPool(2);
        // 2 建立自定義類物件
        MyCallable mc = new MyCallable();
        // 3 提交 Callable 物件
        es.submit(mc);
        es.submit(mc);
    }
}

案例:使用兩個執行緒分別求 1-100 的和(泛型的使用)

public class MyCallable implements Callable<Integer> {
    private int num;

    public MyCallable(int num) {
        this.num = num;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i < num; i++) {
            sum += i;
        }
        return sum;
    }
}
public class MyCallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(2);
        MyCallable mc = new MyCallable(100);
        MyCallable mc2 = new MyCallable(200);

        Future<Integer> future = es.submit(mc);
        Future<Integer> future2 = es.submit(mc2);

        int num = future.get();
        int num2 = future2.get();

        System.out.println(num);
        System.out.println(num2);

        es.shutdown();
    }
}

使用匿名內部類實現多執行緒
匿名內部類格式:

new 類名或介面名(){
	重寫方法;
};
new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 30; i++) {
                    System.out.println(Thread.currentThread().getName() + i);
                }
            }
        };

        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 30; i++) {
                            System.out.println(Thread.currentThread().getName() + i);
                        }
                    }
                }
        ){};

        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 30; i++) {
                            System.out.println("hello" + i);
                        }
                    }
                }
            ){
            @Override
            public void run() {
                for (int i = 0; i < 30; i++) {
                    System.out.println("world" + i);
                }
            }
        };

注:當同時重寫了 Thread類 和 Runnable介面 中的 run() 方法,執行時執行的是 Thread類 中的 run() 方法。

定時器
定時器是一個應用十分廣泛的執行緒工具,可用於排程多個定時任務以後臺的方式執行,可以通過 Timer 和 TimerTask 類來實現定義和排程的功能。
概念:可以指定程式在某個指定的時間做某件工作。
Timer :執行緒的工具,用於在後臺執行緒中安排將來執行的任務。可以將任務安排為一次性執行,或者以固定間隔重複執行。
方法:
Timer():建立一個新的定時器。
public void schedule(TimerTask task, long delay):在指定毫秒時間後執行task任務。
public void schedule(TimerTask task, long delay, long period):在指定毫秒時間後執行task任務,並在指定間隔時間後再次執行。
TimerTask :可由 Timer 一次性或重複執行的任務。
方法:
public boolean cancel():取消此定時器任務。
public abstract void run():重寫run()方法,重寫的內容即要執行的任務。

public class TimerDemo {
    public static void main(String[] args) {
        // 建立定時器物件
        Timer timer = new Timer();
        // 執行 Task 任務
        timer.schedule(new MyTask(timer), 3000);
    }
}
class MyTask extends TimerTask {
    private Timer timer;
    public MyTask(Timer timer) {
        this.timer = timer;
    }
    @Override
    public void run() {
        System.out.println("有內鬼,終止交易");
        timer.cancel();
    }
}

執行以上程式碼,可以看到三秒之後執行 run() 方法中的語句。

我是快鬥,請多多指教!

相關文章