(一)java多線程之Thread


Thread類

學習java線程的開發者,首先遇到的第一個類就是Thread,通過使用Thread類,我們就可以啓動,停止,中斷一個線程. 在同一個時間片裏, 可能會有多個線程在執行, 每個線程都擁有它自己的方法調用堆棧, 參數和變量.每個app至少會有一個線程--主線程(main thread).

創建一個線程

java創建線程有兩種方式

  1. 創建一個繼承Thread的子類,並實現run方法
  2. 使用Thread的構造方法public Thread(Runnable target)創建,這個需要傳入一個實現Runnable接口的子類

實現

下面我們分別以這兩種方式實現一下.

  • 編寫SubThread繼承Thread,並覆蓋run方法 SubThread.java
public class SubThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i ++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }

    public static void main(String[] args) {
        System.out.println("begin main");
        SubThread sub = new SubThread();
        sub.start();
        System.out.println("end main");
    }
}
  • 編寫SubRunnable實現Runnable,然後使用構造器Thread(Runnable) 創建一個線程
public class SubRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i ++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
    public static void main(String[] args) {
        System.out.println("begin main");
        Thread thread = new Thread(new SubRunnable());
        thread.start();
        System.out.println("end main");
    }
}

區別

  • 使用第一種方法創建的話,你可以在run方法中,可以用this直接調用線程的方法,比如獲取線程的id-->this.getId()
  • 而使用第二方法創建線程,在run中,this對象壓根就沒有getId()這個方法,這個時候你只能用Thread.cuurentThread()這個靜態方法獲取該線程.
  • 在這裏一般推薦使用第二種方法創建,因爲這樣比較符合面對象的思路,Thread就只負責線程的啓動,停止,中斷等操作,而Runnable就只負責線程要運行某一個具體任務.

不管使用那種方式創建線程,都可以調用Thread.cuurentThread()獲取當前的線程
還有,Thread其實也是Runnable的一個子類
除了上面兩種創建方法,其中還有另外一種方法創建線程,那就是實現ThreadFactory接口,這種比較適合批量生成某一種規格的線程

讓線程"睡"一會

調用線程的Thread.sleep()方法會讓線程睡眠一段時間,這個時候線程會掛起,然後將CPU的時間片轉移給其他線程,讓其他線程獲得執行的機會.

Thread.sleep()接收一個毫秒值做完參數,並拋出一個InterruptedException異常.

停止線程

不管是使用哪一種方法創建線程,run方法的任務執行完了,線程就自動停止.
如果想在中途就停止線程,有下面幾種方式

  • 調用線程的interrupt()方法,這時線程的中斷位會被標識,並拋出InterruptedException,例如:
public class StopThread1 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("begin main");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

                for (int i = 0; i < 10; i ++){
                    try {
                        Thread.sleep(100);
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    } catch (InterruptedException e) {
                        break;
                    }
                }
            }
        });
        thread.start();
        System.out.println("main sleep 500ms");
        Thread.sleep(500);
        thread.interrupt();
        System.out.println("end main");
    }
}

在調用thread.interrupt();這個語句時,會對該線程的中斷狀態標識爲true,然後在拋出InterruptedException異常時,會清空該中斷位.
修改程序,在拋出InterruptedException中添加System.out.println("InterruptedException:" + Thread.currentThread().isInterrupted());,然後再thread.interrupt();後面添加System.out.println("thread.isInterrupted:" + thread.isInterrupted());.然後運行程序.

這時候運行結果有可能打印出thread.isInterrupted:true;InterruptedException:false或者打印出thread.isInterrupted:false;InterruptedException:false,運行多次結果都有可能不一致,這個是因爲主線程和子線程都通知在執行,還沒有來的及執行主線程的打印語句,子線程異常中的打印語句就已經執行了.

  • 可以在線程中加一個boolean成員變量,提供setter方法,然後在run方法中判斷該變量是否爲true,若爲true則停止線程,否則繼續
public class StopThread2 {
    public static class StopRunnable implements Runnable{
        private boolean isStop = false;

        public void setStop(){
            this.isStop = true;
        }
        @Override
        public void run() {
            int count = 0;
            while (!isStop){
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + ":" + count++);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("begin main");
        StopRunnable stop = new StopRunnable();
        Thread thread = new Thread(stop);
        thread.start();
        Thread.sleep(200);
        stop.setStop();
        System.out.println("end main");
    }
}

線程的屬性

  • id: 通過Thread.getId()可以獲取線程的id,線程的id是一個自增長的long, 不能修改
  • name: 通過Thread.getName(), 用一個字符串來標識線程的名字,可以通過Thread.setName()或部分構造器修改線程的名字
  • priority: 線程的優先級,線程創建默認優先級爲5, 最小爲優先級爲1, 最大爲10.優先級大的線程有機會先執行.但具體那個線程先執行還是要看CPU的心情了.
  • state: 線程的狀態, 線程的狀態有以下幾種
    • Thread.State.NEW新建狀態:這個是線程已經被創建但還沒有調用'start()'方法時的狀態
    • Thread.State.RUNNABLE運行狀態 當前線程已經在JVM中執行
    • Thread.State.BLOCKED阻塞狀態 表示當前線程在等待進入一個同步塊或同步方法,也可以等到一個同步快被提交. 常見的有IO阻塞等.
    • Thread.State.WAITING等待狀態 但線程調用Object.wait(),Thread.join(),LockSupport.park()就會進入等待狀態.當前線程在等待其他線程執行某一個特定操作.比如:當前線程執行Object.wait(),那麼就需要其他線程執行Object.notify()Object.notifyAll(),如果線程執行了Thread.join(),則需要等到指定的線程執行結束.
    • Thread.State.TIMED_WAITING有時間的等待 線程在等待某一個等待的時間.比如,線程執行了Thread.sleep,Object.wait(long),Thread.join(long)
    • Thread.State.TERMINATED終結 線程已經執行完畢.
  • daemon: 這個用來標識線程爲守護線程或非守護線程的,默認創建的線程都是非守護線程.應用程序所有的非守護線程執行完畢之後,則程序就停止運行.比如主線程都是非守護線程,所以主線程會等到主線程的所有語句執行完成,程序纔會停止運行.JVM的資源回收則是一個守護線程.
public class TestDaemonThread {
    public static void main(String[] args) {
        System.out.println("start main");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i ++){
                    try {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                        Thread.sleep(10);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();
        System.out.println("end main");
    }
}

該例子中,程序必須等到主線程和子線程同時執行完成纔會停止,因爲默認創建的線程都是非守護線程,如果在thread.start();前加入thread.setDaemon(true);, 那麼程序不會等子線程執行完才結束程序的.

Thread.join()

等到某線程執行完畢纔開始執行,如果調用Thread.join(long)則表示等到某線程執行完畢或指定的超時時間結束後纔開始執行

public class ThreadJoinTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            for (int i = 0;i < 10; i ++){
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "thread1");
        Thread thread2 = new Thread(() -> {
            try {
                thread1.join();
                for (int i = 0;i < 10; i ++){
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread2");
        thread1.start();
        thread2.start();

    }
}

上面的例子,thread2線程會等thread1執行完之後纔開始執行

Thread.yield

這個方法標識當前線程會按時線程調度者讓其他線程先執行.但CPU是否讓其他線程優先執行,還是要看CPU的心情了.

線程的異常

如果線程發現一些運行時異常而沒有在run方法俘獲,會怎麼辦?

程序會打印出一推錯誤堆棧,如果我們先把線程的錯誤按照某種可讀的方式打印到問題,但又不想在每個run方法中增加try{...}catch(Exception e){...}怎麼辦?
我們查看Thread類的源碼發現,在Thread中有一個內部接口UncaughtExceptionHandler,這個正是我們所需要的.實現這個接口,並調用Thread.setUncaughtExceptionHandler,那麼但線程出現時,則會回調uncaughtException方法

public class ThreadExceptionTest {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("begin main");
        Thread thread = new Thread(() -> {
            int i = 1 / 0;
        },"myThread");
        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(String.format("%s發生異常%s", t.getName(), e.getMessage()));
            }
        });
        thread.start();
        System.out.println("end main");
    }
}
發佈了10 篇原創文章 · 獲贊 6 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章