Java 線程基礎(chǔ)

?? 本文以及示例源碼已歸檔在 javacore

一内贮、線程簡介

什么是進程

簡言之,進程可視為一個正在運行的程序汞斧。它是系統(tǒng)運行程序的基本單位夜郁,因此進程是動態(tài)的啤它。進程是具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動谚咬。進程是操作系統(tǒng)進行資源分配的基本單位。

什么是線程

線程是操作系統(tǒng)進行調(diào)度的基本單位锻霎。線程也叫輕量級進程(Light Weight Process)庙睡,在一個進程里可以創(chuàng)建多個線程事富,這些線程都擁有各自的計數(shù)器、堆棧和局部變量等屬性乘陪,并且能夠訪問共享的內(nèi)存變量统台。

進程和線程的區(qū)別

  • 一個程序至少有一個進程,一個進程至少有一個線程啡邑。
  • 線程比進程劃分更細贱勃,所以執(zhí)行開銷更小,并發(fā)性更高谣拣。
  • 進程是一個實體募寨,擁有獨立的資源;而同一個進程中的多個線程共享進程的資源森缠。

二拔鹰、線程基本用法

線程(Thread)基本方法清單:

方法 描述
run 線程的執(zhí)行實體。
start 線程的啟動方法贵涵。
currentThread 返回對當前正在執(zhí)行的線程對象的引用列肢。
setName 設(shè)置線程名稱。
getName 獲取線程名稱宾茂。
setPriority 設(shè)置線程優(yōu)先級瓷马。Java 中的線程優(yōu)先級的范圍是 [1,10],一般來說跨晴,高優(yōu)先級的線程在運行時會具有優(yōu)先權(quán)欧聘。可以通過 thread.setPriority(Thread.MAX_PRIORITY) 的方式設(shè)置端盆,默認優(yōu)先級為 5怀骤。
getPriority 獲取線程優(yōu)先級费封。
setDaemon 設(shè)置線程為守護線程。
isDaemon 判斷線程是否為守護線程蒋伦。
isAlive 判斷線程是否啟動弓摘。
interrupt 中斷另一個線程的運行狀態(tài)。
interrupted 測試當前線程是否已被中斷痕届。通過此方法可以清除線程的中斷狀態(tài)韧献。換句話說,如果要連續(xù)調(diào)用此方法兩次研叫,則第二次調(diào)用將返回 false(除非當前線程在第一次調(diào)用清除其中斷狀態(tài)之后且在第二次調(diào)用檢查其狀態(tài)之前再次中斷)锤窑。
join 可以使一個線程強制運行,線程強制運行期間蓝撇,其他線程無法運行果复,必須等待此線程完成之后才可以繼續(xù)執(zhí)行陈莽。
Thread.sleep 靜態(tài)方法渤昌。將當前正在執(zhí)行的線程休眠。
Thread.yield 靜態(tài)方法走搁。將當前正在執(zhí)行的線程暫停独柑,讓其他線程執(zhí)行。

創(chuàng)建線程

創(chuàng)建線程有三種方式:

  • 繼承 Thread
  • 實現(xiàn) Runnable 接口
  • 實現(xiàn) Callable 接口

繼承 Thread 類

通過繼承 Thread 類創(chuàng)建線程的步驟:

  1. 定義 Thread 類的子類私植,并覆寫該類的 run 方法忌栅。run 方法的方法體就代表了線程要完成的任務(wù),因此把 run 方法稱為執(zhí)行體曲稼。
  2. 創(chuàng)建 Thread 子類的實例索绪,即創(chuàng)建了線程對象。
  3. 調(diào)用線程對象的 start 方法來啟動該線程贫悄。
public class ThreadDemo {

    public static void main(String[] args) {
        // 實例化對象
        MyThread tA = new MyThread("Thread 線程-A");
        MyThread tB = new MyThread("Thread 線程-B");
        // 調(diào)用線程主體
        tA.start();
        tB.start();
    }

    static class MyThread extends Thread {

        private int ticket = 5;

        MyThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            while (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");
                ticket--;
            }
        }

    }

}

實現(xiàn) Runnable 接口

實現(xiàn) Runnable 接口優(yōu)于繼承 Thread瑞驱,因為:

  • Java 不支持多重繼承,所有的類都只允許繼承一個父類窄坦,但可以實現(xiàn)多個接口唤反。如果繼承了 Thread 類就無法繼承其它類,這不利于擴展鸭津。
  • 類可能只要求可執(zhí)行就行彤侍,繼承整個 Thread 類開銷過大。

通過實現(xiàn) Runnable 接口創(chuàng)建線程的步驟:

  1. 定義 Runnable 接口的實現(xiàn)類逆趋,并覆寫該接口的 run 方法盏阶。該 run 方法的方法體同樣是該線程的線程執(zhí)行體。
  2. 創(chuàng)建 Runnable 實現(xiàn)類的實例闻书,并以此實例作為 Thread 的 target 來創(chuàng)建 Thread 對象名斟,該 Thread 對象才是真正的線程對象吴汪。
  3. 調(diào)用線程對象的 start 方法來啟動該線程。
public class RunnableDemo {

    public static void main(String[] args) {
        // 實例化對象
        Thread tA = new Thread(new MyThread(), "Runnable 線程-A");
        Thread tB = new Thread(new MyThread(), "Runnable 線程-B");
        // 調(diào)用線程主體
        tA.start();
        tB.start();
    }

    static class MyThread implements Runnable {

        private int ticket = 5;

        @Override
        public void run() {
            while (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");
                ticket--;
            }
        }

    }

}

實現(xiàn) Callable 接口

繼承 Thread 類 和 實現(xiàn) Callable 接口這兩種創(chuàng)建線程的方式都沒有返回值蒸眠。所以漾橙,線程執(zhí)行完后,無法得到執(zhí)行結(jié)果楞卡。但如果期望得到執(zhí)行結(jié)果該怎么做霜运?

為了解決這個問題,Java 1.5 后蒋腮,提供了 Callable 接口和 Future 接口淘捡,通過它們,可以在線程執(zhí)行結(jié)束后池摧,返回執(zhí)行結(jié)果焦除。

通過實現(xiàn) Callable 接口創(chuàng)建線程的步驟:

  1. 創(chuàng)建 Callable 接口的實現(xiàn)類,并實現(xiàn) call 方法作彤。該 call 方法將作為線程執(zhí)行體膘魄,并且有返回值。
  2. 創(chuàng)建 Callable 實現(xiàn)類的實例竭讳,使用 FutureTask 類來包裝 Callable 對象创葡,該 FutureTask 對象封裝了該 Callable 對象的 call 方法的返回值。
  3. 使用 FutureTask 對象作為 Thread 對象的 target 創(chuàng)建并啟動新線程绢慢。
  4. 調(diào)用 FutureTask 對象的 get 方法來獲得線程執(zhí)行結(jié)束后的返回值灿渴。
public class CallableDemo {

    public static void main(String[] args) {
        Callable<Long> callable = new MyThread();
        FutureTask<Long> future = new FutureTask<>(callable);
        new Thread(future, "Callable 線程").start();
        try {
            System.out.println("任務(wù)耗時:" + (future.get() / 1000000) + "毫秒");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    static class MyThread implements Callable<Long> {

        private int ticket = 10000;

        @Override
        public Long call() {
            long begin = System.nanoTime();
            while (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");
                ticket--;
            }

            long end = System.nanoTime();
            return (end - begin);
        }

    }

}

FAQ

startrun 方法有什么區(qū)別
  • run 方法是線程的執(zhí)行體。
  • start 方法會啟動線程胰舆,然后 JVM 會讓這個線程去執(zhí)行 run 方法骚露。
可以直接調(diào)用 Thread 類的 run 方法么
  • 可以。但是如果直接調(diào)用 Threadrun 方法缚窿,它的行為就會和普通的方法一樣棘幸。
  • 為了在新的線程中執(zhí)行我們的代碼,必須使用 Threadstart 方法滨攻。

線程休眠

使用 Thread.sleep 方法可以使得當前正在執(zhí)行的線程進入休眠狀態(tài)够话。

使用 Thread.sleep 需要向其傳入一個整數(shù)值,這個值表示線程將要休眠的毫秒數(shù)光绕。

Thread.sleep 方法可能會拋出 InterruptedException女嘲,因為異常不能跨線程傳播回 main 中,因此必須在本地進行處理诞帐。線程中拋出的其它異常也同樣需要在本地進行處理欣尼。

public class ThreadSleepDemo {

    public static void main(String[] args) {
        new Thread(new MyThread("線程A", 500)).start();
        new Thread(new MyThread("線程B", 1000)).start();
        new Thread(new MyThread("線程C", 1500)).start();
    }

    static class MyThread implements Runnable {

        /** 線程名稱 */
        private String name;

        /** 休眠時間 */
        private int time;

        private MyThread(String name, int time) {
            this.name = name;
            this.time = time;
        }

        @Override
        public void run() {
            try {
                // 休眠指定的時間
                Thread.sleep(this.time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.name + "休眠" + this.time + "毫秒。");
        }

    }

}

線程禮讓

Thread.yield 方法的調(diào)用聲明了當前線程已經(jīng)完成了生命周期中最重要的部分,可以切換給其它線程來執(zhí)行 愕鼓。

該方法只是對線程調(diào)度器的一個建議钙态,而且也只是建議具有相同優(yōu)先級的其它線程可以運行。

public class ThreadYieldDemo {

    public static void main(String[] args) {
        MyThread t = new MyThread();
        new Thread(t, "線程A").start();
        new Thread(t, "線程B").start();
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "運行菇晃,i = " + i);
                if (i == 2) {
                    System.out.print("線程禮讓:");
                    Thread.yield();
                }
            }
        }
    }
}

終止線程

Thread 中的 stop 方法有缺陷册倒,已廢棄

使用 Thread.stop 停止線程會導(dǎo)致它解鎖所有已鎖定的監(jiān)視器(由于未經(jīng)檢查的 ThreadDeath 異常會在堆棧中傳播磺送,這是自然的結(jié)果)驻子。 如果先前由這些監(jiān)視器保護的任何對象處于不一致狀態(tài),則損壞的對象將對其他線程可見估灿,從而可能導(dǎo)致任意行為崇呵。Thread.stop 的許多用法應(yīng)由僅修改某些變量以指示目標線程應(yīng)停止運行的代碼代替。 目標線程應(yīng)定期檢查此變量馅袁,如果該變量指示要停止運行域慷,則應(yīng)按有序方式從其運行方法返回。如果目標線程等待很長時間(例如汗销,在條件變量上)犹褒,則應(yīng)使用中斷方法來中斷等待。

當一個線程運行時大溜,另一個線程可以直接通過 interrupt 方法中斷其運行狀態(tài)化漆。

public class ThreadInterruptDemo {

    public static void main(String[] args) {
        MyThread mt = new MyThread(); // 實例化Runnable子類對象
        Thread t = new Thread(mt, "線程"); // 實例化Thread對象
        t.start(); // 啟動線程
        try {
            Thread.sleep(2000); // 線程休眠2秒
        } catch (InterruptedException e) {
            System.out.println("3、休眠被終止");
        }
        t.interrupt(); // 中斷線程執(zhí)行
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            System.out.println("1钦奋、進入run()方法");
            try {
                Thread.sleep(10000); // 線程休眠10秒
                System.out.println("2、已經(jīng)完成了休眠");
            } catch (InterruptedException e) {
                System.out.println("3疙赠、休眠被終止");
                return; // 返回調(diào)用處
            }
            System.out.println("4付材、run()方法正常結(jié)束");
        }
    }
}

如果一個線程的 run 方法執(zhí)行一個無限循環(huán),并且沒有執(zhí)行 sleep 等會拋出 InterruptedException 的操作圃阳,那么調(diào)用線程的 interrupt 方法就無法使線程提前結(jié)束厌衔。

但是調(diào)用 interrupt 方法會設(shè)置線程的中斷標記,此時調(diào)用 interrupted 方法會返回 true捍岳。因此可以在循環(huán)體中使用 interrupted 方法來判斷線程是否處于中斷狀態(tài)富寿,從而提前結(jié)束線程。

安全地終止線程有兩種方法:

  • 定義 volatile 標志位锣夹,在 run 方法中使用標志位控制線程終止
  • 使用 interrupt 方法和 Thread.interrupted 方法配合使用來控制線程終止

示例:使用 volatile 標志位控制線程終止

public class ThreadStopDemo2 {

    public static void main(String[] args) throws Exception {
        MyTask task = new MyTask();
        Thread thread = new Thread(task, "MyTask");
        thread.start();
        TimeUnit.MILLISECONDS.sleep(50);
        task.cancel();
    }

    private static class MyTask implements Runnable {

        private volatile boolean flag = true;

        private volatile long count = 0L;

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 線程啟動");
            while (flag) {
                System.out.println(count++);
            }
            System.out.println(Thread.currentThread().getName() + " 線程終止");
        }

        /**
         * 通過 volatile 標志位來控制線程終止
         */
        public void cancel() {
            flag = false;
        }

    }

}

示例:使用 interrupt 方法和 Thread.interrupted 方法配合使用來控制線程終止

public class ThreadStopDemo3 {

    public static void main(String[] args) throws Exception {
        MyTask task = new MyTask();
        Thread thread = new Thread(task, "MyTask");
        thread.start();
        TimeUnit.MILLISECONDS.sleep(50);
        thread.interrupt();
    }

    private static class MyTask implements Runnable {

        private volatile long count = 0L;

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 線程啟動");
            // 通過 Thread.interrupted 和 interrupt 配合來控制線程終止
            while (!Thread.interrupted()) {
                System.out.println(count++);
            }
            System.out.println(Thread.currentThread().getName() + " 線程終止");
        }
    }
}

守護線程

什么是守護線程页徐?

  • 守護線程(Daemon Thread)是在后臺執(zhí)行并且不會阻止 JVM 終止的線程。當所有非守護線程結(jié)束時银萍,程序也就終止变勇,同時會殺死所有守護線程。
  • 與守護線程(Daemon Thread)相反的贴唇,叫用戶線程(User Thread)搀绣,也就是非守護線程飞袋。

為什么需要守護線程?

  • 守護線程的優(yōu)先級比較低链患,用于為系統(tǒng)中的其它對象和線程提供服務(wù)巧鸭。典型的應(yīng)用就是垃圾回收器。

如何使用守護線程麻捻?

  • 可以使用 isDaemon 方法判斷線程是否為守護線程蹄皱。
  • 可以使用 setDaemon 方法設(shè)置線程為守護線程。
    • 正在運行的用戶線程無法設(shè)置為守護線程芯肤,所以 setDaemon 必須在 thread.start 方法之前設(shè)置巷折,否則會拋出 llegalThreadStateException 異常;
    • 一個守護線程創(chuàng)建的子線程依然是守護線程崖咨。
    • 不要認為所有的應(yīng)用都可以分配給守護線程來進行服務(wù)锻拘,比如讀寫操作或者計算邏輯。
public class ThreadDaemonDemo {

    public static void main(String[] args) {
        Thread t = new Thread(new MyThread(), "線程");
        t.setDaemon(true); // 此線程在后臺運行
        System.out.println("線程 t 是否是守護進程:" + t.isDaemon());
        t.start(); // 啟動線程
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            while (true) {
                System.out.println(Thread.currentThread().getName() + "在運行击蹲。");
            }
        }
    }
}

參考閱讀:Java 中守護線程的總結(jié)

FAQ

sleep署拟、yield、join 方法有什么區(qū)別

  • yield 方法
    • yield 方法會 讓線程從 Running 狀態(tài)轉(zhuǎn)入 Runnable 狀態(tài)歌豺。
    • 當調(diào)用了 yield 方法后推穷,只有與當前線程相同或更高優(yōu)先級的Runnable 狀態(tài)線程才會獲得執(zhí)行的機會
  • sleep 方法
    • sleep 方法會 讓線程從 Running 狀態(tài)轉(zhuǎn)入 Waiting 狀態(tài)类咧。
    • sleep 方法需要指定等待的時間馒铃,超過等待時間后,JVM 會將線程從 Waiting 狀態(tài)轉(zhuǎn)入 Runnable 狀態(tài)痕惋。
    • 當調(diào)用了 sleep 方法后区宇,無論什么優(yōu)先級的線程都可以得到執(zhí)行機會
    • sleep 方法不會釋放“鎖標志”值戳,也就是說如果有 synchronized 同步塊议谷,其他線程仍然不能訪問共享數(shù)據(jù)。
  • join
    • join 方法會 讓線程從 Running 狀態(tài)轉(zhuǎn)入 Waiting 狀態(tài)堕虹。
    • 當調(diào)用了 join 方法后卧晓,當前線程必須等待調(diào)用 join 方法的線程結(jié)束后才能繼續(xù)執(zhí)行

為什么 sleep 和 yield 方法是靜態(tài)的

Thread 類的 sleepyield 方法將處理 Running 狀態(tài)的線程赴捞。

所以在其他處于非 Running 狀態(tài)的線程上執(zhí)行這兩個方法是沒有意義的逼裆。這就是為什么這些方法是靜態(tài)的。它們可以在當前正在執(zhí)行的線程中工作螟炫,并避免程序員錯誤的認為可以在其他非運行線程調(diào)用這些方法波附。

Java 線程是否按照線程優(yōu)先級嚴格執(zhí)行

即使設(shè)置了線程的優(yōu)先級,也無法保證高優(yōu)先級的線程一定先執(zhí)行

原因在于線程優(yōu)先級依賴于操作系統(tǒng)的支持掸屡,然而封寞,不同的操作系統(tǒng)支持的線程優(yōu)先級并不相同,不能很好的和 Java 中線程優(yōu)先級一一對應(yīng)仅财。

三狈究、線程間通信

當多個線程可以一起工作去解決某個問題時,如果某些部分必須在其它部分之前完成盏求,那么就需要對線程進行協(xié)調(diào)抖锥。

wait/notify/notifyAll

  • wait - wait 方法使得線程釋放其占有的對象鎖,讓線程從 Running 狀態(tài)轉(zhuǎn)入 Waiting 狀態(tài)碎罚,并等待 notify / notifyAll 來喚醒 磅废。如果沒有釋放鎖,那么其它線程就無法進入對象的同步方法或者同步控制塊中荆烈,那么就無法執(zhí)行 notify 或者 notifyAll 來喚醒掛起的線程拯勉,造成死鎖。
  • notify - 喚醒一個正在 Waiting 狀態(tài)的線程憔购,并讓它拿到對象鎖宫峦,具體喚醒哪一個線程由 JVM 控制 。
  • notifyAll - 喚醒所有正在 Waiting 狀態(tài)的線程玫鸟,接下來它們需要競爭對象鎖导绷。

注意:

  • waitnotify屎飘、notifyAll 都是 Object 類中的方法妥曲,而非 Thread
  • wait枚碗、notify逾一、notifyAll 只能用在 synchronized 方法或者 synchronized 代碼塊中使用,否則會在運行時拋出 IllegalMonitorStateException肮雨。

為什么 waitnotify箱玷、notifyAll 不定義在 Thread 中怨规?為什么 waitnotify锡足、notifyAll 要配合 synchronized 使用波丰?

首先,需要了解幾個基本知識點:

  • 每一個 Java 對象都有一個與之對應(yīng)的 監(jiān)視器(monitor)
  • 每一個監(jiān)視器里面都有一個 對象鎖 舶得、一個 等待隊列掰烟、一個 同步隊列

了解了以上概念,我們回過頭來理解前面兩個問題。

為什么這幾個方法不定義在 Thread 中纫骑?

由于每個對象都擁有對象鎖蝎亚,讓當前線程等待某個對象鎖,自然應(yīng)該基于這個對象(Object)來操作先馆,而非使用當前線程(Thread)來操作发框。因為當前線程可能會等待多個線程的鎖,如果基于線程(Thread)來操作煤墙,就非常復(fù)雜了梅惯。

為什么 waitnotify仿野、notifyAll 要配合 synchronized 使用铣减?

如果調(diào)用某個對象的 wait 方法,當前線程必須擁有這個對象的對象鎖脚作,因此調(diào)用 wait 方法必須在 synchronized 方法和 synchronized 代碼塊中葫哗。

生產(chǎn)者、消費者模式是 wait鳖枕、notify魄梯、notifyAll 的一個經(jīng)典使用案例:

public class ThreadWaitNotifyDemo02 {

    private static final int QUEUE_SIZE = 10;
    private static final PriorityQueue<Integer> queue = new PriorityQueue<>(QUEUE_SIZE);

    public static void main(String[] args) {
        new Producer("生產(chǎn)者A").start();
        new Producer("生產(chǎn)者B").start();
        new Consumer("消費者A").start();
        new Consumer("消費者B").start();
    }

    static class Consumer extends Thread {

        Consumer(String name) {
            super(name);
        }

        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    while (queue.size() == 0) {
                        try {
                            System.out.println("隊列空,等待數(shù)據(jù)");
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notifyAll();
                        }
                    }
                    queue.poll(); // 每次移走隊首元素
                    queue.notifyAll();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 從隊列取走一個元素宾符,隊列當前有:" + queue.size() + "個元素");
                }
            }
        }
    }

    static class Producer extends Thread {

        Producer(String name) {
            super(name);
        }

        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    while (queue.size() == QUEUE_SIZE) {
                        try {
                            System.out.println("隊列滿酿秸,等待有空余空間");
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notifyAll();
                        }
                    }
                    queue.offer(1); // 每次插入一個元素
                    queue.notifyAll();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 向隊列取中插入一個元素,隊列當前有:" + queue.size() + "個元素");
                }
            }
        }
    }
}

join

在線程操作中魏烫,可以使用 join 方法讓一個線程強制運行辣苏,線程強制運行期間,其他線程無法運行哄褒,必須等待此線程完成之后才可以繼續(xù)執(zhí)行稀蟋。

public class ThreadJoinDemo {

    public static void main(String[] args) {
        MyThread mt = new MyThread(); // 實例化Runnable子類對象
        Thread t = new Thread(mt, "mythread"); // 實例化Thread對象
        t.start(); // 啟動線程
        for (int i = 0; i < 50; i++) {
            if (i > 10) {
                try {
                    t.join(); // 線程強制運行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Main 線程運行 --> " + i);
        }
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName() + " 運行,i = " + i); // 取得當前線程的名字
            }
        }
    }
}

管道

管道輸入/輸出流和普通的文件輸入/輸出流或者網(wǎng)絡(luò)輸入/輸出流不同之處在于呐赡,它主要用于線程之間的數(shù)據(jù)傳輸退客,而傳輸?shù)拿浇闉閮?nèi)存。
管道輸入/輸出流主要包括了如下 4 種具體實現(xiàn):PipedOutputStream链嘀、PipedInputStream萌狂、PipedReaderPipedWriter,前兩種面向字節(jié)怀泊,而后兩種面向字符茫藏。

public class Piped {

    public static void main(String[] args) throws Exception {
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();
        // 將輸出流和輸入流進行連接,否則在使用時會拋出IOException
        out.connect(in);
        Thread printThread = new Thread(new Print(in), "PrintThread");
        printThread.start();
        int receive = 0;
        try {
            while ((receive = System.in.read()) != -1) {
                out.write(receive);
            }
        } finally {
            out.close();
        }
    }

    static class Print implements Runnable {

        private PipedReader in;

        Print(PipedReader in) {
            this.in = in;
        }

        public void run() {
            int receive = 0;
            try {
                while ((receive = in.read()) != -1) {
                    System.out.print((char) receive);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

四霹琼、線程狀態(tài)

java.lang.Thread.State 中定義了 6 種不同的線程狀態(tài)务傲,在給定的一個時刻凉当,線程只能處于其中的一個狀態(tài)。

以下是各狀態(tài)的說明售葡,以及狀態(tài)間的聯(lián)系:

  • 新建(New) - 尚未調(diào)用 start 方法的線程處于此狀態(tài)看杭。此狀態(tài)意味著:創(chuàng)建的線程尚未啟動

  • 可運行(Runnable) - 已經(jīng)調(diào)用了 start 方法的線程處于此狀態(tài)天通。此狀態(tài)意味著:線程已經(jīng)在 JVM 中運行泊窘。但是在操作系統(tǒng)層面,它可能處于運行狀態(tài)像寒,也可能等待資源調(diào)度(例如處理器資源)烘豹,資源調(diào)度完成就進入運行狀態(tài)。所以該狀態(tài)的可運行是指可以被運行诺祸,具體有沒有運行要看底層操作系統(tǒng)的資源調(diào)度携悯。

  • 阻塞(Blocked) - 請求獲取 monitor lock 從而進入 synchronized 函數(shù)或者代碼塊,但是其它線程已經(jīng)占用了該 monitor lock筷笨,所以處于阻塞狀態(tài)憔鬼。要結(jié)束該狀態(tài)進入 Runnable,從而需要其他線程釋放 monitor lock胃夏。此狀態(tài)意味著:線程處于被阻塞狀態(tài)轴或。

  • 等待(Waiting) - 此狀態(tài)意味著:線程等待被其他線程顯式地喚醒。 阻塞和等待的區(qū)別在于仰禀,阻塞是被動的照雁,它是在等待獲取 monitor lock。而等待是主動的答恶,通過調(diào)用 Object.wait 等方法進入饺蚊。

    進入方法 退出方法
    沒有設(shè)置 Timeout 參數(shù)的 Object.wait 方法 Object.notify / Object.notifyAll
    沒有設(shè)置 Timeout 參數(shù)的 Thread.join 方法 被調(diào)用的線程執(zhí)行完畢
    LockSupport.park 方法 LockSupport.unpark
  • 定時等待(Timed waiting) - 此狀態(tài)意味著:無需等待其它線程顯式地喚醒,在一定時間之后會被系統(tǒng)自動喚醒悬嗓。

    進入方法 退出方法
    Thread.sleep 方法 時間結(jié)束
    設(shè)置了 Timeout 參數(shù)的 Object.wait 方法 時間結(jié)束 / Object.notify / Object.notifyAll
    設(shè)置了 Timeout 參數(shù)的 Thread.join 方法 時間結(jié)束 / 被調(diào)用的線程執(zhí)行完畢
    LockSupport.parkNanos 方法 LockSupport.unpark
    LockSupport.parkUntil 方法 LockSupport.unpark
  • 終止(Terminated) - 線程 run 方法執(zhí)行結(jié)束污呼,或者因異常退出了 run 方法。此狀態(tài)意味著:線程結(jié)束了生命周期包竹。

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堰氓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子苹享,更是在濱河造成了極大的恐慌双絮,老刑警劉巖浴麻,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異囤攀,居然都是意外死亡软免,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門焚挠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膏萧,“玉大人,你說我怎么就攤上這事蝌衔¢环海” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵噩斟,是天一觀的道長曹锨。 經(jīng)常有香客問我,道長剃允,這世上最難降的妖魔是什么沛简? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮斥废,結(jié)果婚禮上椒楣,老公的妹妹穿的比我還像新娘。我一直安慰自己牡肉,他們只是感情好捧灰,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荚板,像睡著了一般凤壁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跪另,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天拧抖,我揣著相機與錄音,去河邊找鬼免绿。 笑死唧席,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的嘲驾。 我是一名探鬼主播淌哟,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辽故!你這毒婦竟也來了徒仓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤誊垢,失蹤者是張志新(化名)和其女友劉穎掉弛,沒想到半個月后症见,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡殃饿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年谋作,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乎芳。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡遵蚜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奈惑,到底是詐尸還是另有隱情吭净,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布携取,位于F島的核電站攒钳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏雷滋。R本人自食惡果不足惜不撑,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晤斩。 院中可真熱鬧焕檬,春花似錦、人聲如沸澳泵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兔辅。三九已至腊敲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間维苔,已是汗流浹背碰辅。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留介时,地道東北人没宾。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像沸柔,于是被迫代替她去往敵國和親循衰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容