線程的啟動與終止

一、線程的創(chuàng)建

1.Oracle 官網(wǎng)描述

There are two ways to create a new thread of execution.

One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started.

The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started.

有兩種方式可以創(chuàng)建新的執(zhí)行線程港粱。一種方法是定義一個 Thread 類的子類,該子類重寫 Thread 類的 run 方法。然后可以分配并啟動子類的實例猜极。創(chuàng)建線程的另一種方法是定義一個實現(xiàn) Runnable 接口的類延届,然后可以分配該類的實例,在創(chuàng)建 Thread 時將其作為參數(shù)傳遞并啟動荔燎。

1.1 繼承 Thread 類

public class ThreadStyle extends Thread {

    @Override
    public void run() {
        System.out.println("用 Thread 方式創(chuàng)建線程");
    }

    public static void main(String[] args) {
        new ThreadStyle().start();
    }
}

定義一個類繼承 Thread 類耻姥,然后重寫 run 方法,直接通過該類的實例啟動線程有咨,輸出結(jié)果為:用 Thread 方式創(chuàng)建線程琐簇。

1.2 實現(xiàn) Runnable 接口

public class RunnableStyle implements Runnable {

    @Override
    public void run() {
        System.out.println("用 Runnable 方式實現(xiàn)線程");
    }

    public static void main(String[] args) {
        new Thread(new RunnableStyle()).start();
    }
}

定義一個類實現(xiàn) Runnable 接口并重寫 run 方法,通過將該 Runnable 實例傳入 Thread 類參數(shù)中啟動線程摔吏,輸出結(jié)果為:用 Runnable 方式實現(xiàn)線程鸽嫂。

1.3 兩種方法對比

準確的講,創(chuàng)建線程都是通過構(gòu)造 Thread 類這一種方式實現(xiàn)征讲,實現(xiàn)線程的執(zhí)行單元有兩種方式据某。

public class Thread implements Runnable {
    /** 省略 */
    
    private Runnable target;
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}
  • 方法一(繼承 Thread 類):Thread 類本身也是實現(xiàn)了 Runnable 接口然后重寫 run 方法的,當(dāng)通過繼承 Thread 類創(chuàng)建線程時诗箍,Thread 類的整個 run 方法都會被重寫癣籽。
  • 方法二(實現(xiàn) Runnable 接口):Thread 類中有一個名為 target 的 Runnable 變量,在 Thread(Runnable target) 構(gòu)造方法中傳入 Runnable 實例會初始化 target 屬性滤祖,通過該方法創(chuàng)建線程筷狼,最終只會調(diào)用 target.run() 方法,并不會重寫整個 run 方法匠童。

通過方法二創(chuàng)建線程的方式其實更好:

  • 實現(xiàn) Runnable 接口的方式與 Thread 類解耦埂材。
  • 接口可以多實現(xiàn),繼承 Thread 類的方式限制了可擴展性汤求。
  • 繼承 Thread 類的話俏险,每次新建線程都會去創(chuàng)建一個獨立的線程,開銷大扬绪,不適合資源共享竖独。實現(xiàn) Runnable 接口的話,則很容易的實現(xiàn)資源共享挤牛,而且可以利用線程池等工具莹痢,大大減小創(chuàng)建線程和銷毀線程帶來的損耗。

同時使用這兩種方法創(chuàng)建線程:

先通過在 Thread 類構(gòu)造器中傳入匿名內(nèi)部類(Runnable 實例)的方式創(chuàng)建線程,然后在此基礎(chǔ)上重寫了 Thread 類的 run 方法竞膳,最終輸出:使用 Thread 方式創(chuàng)建航瞭。因為傳入 Runnable 實例創(chuàng)建線程是調(diào)用 run 方法中的 target.run() 執(zhí)行的,但是后面重寫了 run 方法坦辟,導(dǎo)致此方法失效沧奴。

public class BothRunnableAndThread {

    public static void main(String[] args) {
        new Thread(() -> System.out.println("使用 Runnable 方式創(chuàng)建")) {
            // 重寫了 run 方法,覆蓋了 run 里的三行代碼
            // runnable 傳入進去卻沒有運行
            @Override
            public void run() {
                System.out.println("使用 Thread 方式創(chuàng)建");
            }
        }.start();
    }
}

創(chuàng)建線程的方式通常分為兩類长窄,除此之外滔吠,通過實現(xiàn) Callable 接口、使用線程池等方式也可以創(chuàng)建線程挠日,但是本質(zhì)上都是繼承 Thread 類或?qū)崿F(xiàn) Runnable 接口這兩種方法疮绷。

1.4 線程初始化

不管通過哪種方式創(chuàng)建線程,都會調(diào)用線程初始化函數(shù) init嚣潜,可以通過不同的構(gòu)造函數(shù)初始化線程的部分參數(shù)冬骚。如下 init 函數(shù)所示,一個新構(gòu)造的線程對象是由其父線程來進行空間分配的懂算,而子線程繼承了父線程的 daemon只冻、priority 等屬性,同時還會分配一個唯一的 id 來標識這個線程计技。

public class Thread implements Runnable {
    
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    public Thread(String name) {
        init(null, null, name, 0);
    }
    
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
        // 省略部分代碼
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        // 當(dāng)前線程就是該線程的父線程
        Thread parent = currentThread();
        // 復(fù)制父線程的 daemon 和 priority 屬性
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        this.target = target;
        setPriority(priority);
        // 復(fù)制父線程的 inheritableThreadLocals
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        // 分配線程 ID
        tid = nextThreadID();
    }
}

2.實現(xiàn) Callable 接口

public class CallableDemo implements Callable<String> {

    @Override
    public String call() {
        return "hncboy";
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        FutureTask<String> ft = new FutureTask<>(new CallableStyle());
        Thread thread = new Thread(ft);
        thread.start();
        System.out.println(ft.get());
    }
}

3.使用線程池

public class ThreadPoolDemo {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.submit(new TaskThread());
    }

    private static class TaskThread implements Runnable {

        @Override
        public void run() {
            System.out.println("hncboy");
        }
    }
}

4.使用定時器

public class TimerTaskDemo {

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hncboy");
            }
        }, 1000, 1000);
    }
}

二喜德、線程的啟動

1.start() 方法

1.1 方法含義

啟動新線程:通知 JVM 在有空閑的情況下啟動線程,本質(zhì)是請求 JVM 來運行我們的線程垮媒,線程何時運行由線程調(diào)度器來確定舍悯。該線程啟動的同時會啟動兩個線程:第一個是用來執(zhí)行 start 方法的父線程或主線程,第二個是被創(chuàng)建的子線程睡雇。

準備工作:讓線程處于就緒狀態(tài)(已經(jīng)獲得了除 CPU 以外的其他資源萌衬,如已經(jīng)設(shè)置了上下文,線程狀態(tài)它抱,棧等)秕豫,做完準備工作后,才能被 JVM 或操作系統(tǒng)調(diào)度到執(zhí)行狀態(tài)獲取 CPU 資源观蓄,然后才會執(zhí)行 run 方法混移。

重復(fù)調(diào)用 start() :拋出異常 Exception in thread "main" java.lang.IllegalThreadStateException。一旦線程 start 以后蜘腌,就從 NEW 狀態(tài)進入到其他狀態(tài)沫屡,比如 RUNNABLE饵隙,只有處于 NEW 狀態(tài)的線程才能調(diào)用 start() 方法撮珠。

1.2 原理分析

通過 threadStatus 屬性來判斷是否重復(fù)啟動并拋出異常,實際的啟動方法是 native 方法 start0()。

public class Thread implements Runnable {
    
    /**
     * 線程狀態(tài)芯急,初始化為 0勺届,表示還未啟
     */
    private volatile int threadStatus = 0;
    
    public synchronized void start() {
        // 判斷線程的狀態(tài),也就是判斷是否啟動娶耍,重復(fù)啟動時拋出 IllegalThreadStateException
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        // 將線程加入線程組
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    // 告知線程組該線程啟動失敗
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {}
        }
    }
    
    private native void start0();
}

通過 /src/share/native/java/lang/Thread.c 可知免姿,start0() 方法對應(yīng) JVM_StartThread 方法

static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
};

位于 /src/hotspot/share/prims/jvm.cpp 的 JVM_StartThread 方法中有段注釋

// Since JDK 5 the java.lang.Thread threadStatus is used to prevent
// re-starting an already started thread, so we should usually find
// that the JavaThread is null. However for a JNI attached thread
// there is a small window between the Thread object being created
// (with its JavaThread set) and the update to its threadStatus, so we
// have to check for this

該段注釋說自從 JDK5 后 使用 Thread 類的 threadStatus 屬性去方式線程重復(fù)啟動,接下來看下 /src/share/vm/runtime/thread.cpp 中的 start 方法榕酒,該方法中判斷如果該線程是 Java 線程胚膊,則將該線程的狀態(tài)改為 RUNNABLE。

void Thread::start(Thread* thread) {
  trace("start", thread);
  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
     // 這里調(diào)用 set_thread_status 方法將線程的狀態(tài)修改為 RUNNALBE
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread);
  }
}

2.run() 方法

run() 只是 Thread 類的一個基本方法

public class Thread implements Runnable {
    /** 省略 */
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

3.比較兩方法

輸出:main 和 Thread-0

public class StartAndRunMethod {

    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
        runnable.run();
        new Thread(runnable).start();
    }
}

調(diào)用 start 方法才是真正意義上啟動了一個線程想鹰,會經(jīng)歷線程的各個生命周期紊婉,如果直接調(diào)用 run 方法,則只是普通的調(diào)用方法辑舷,不會通過子線程去調(diào)用喻犁。

三、線程的終止

1.過期的 suspend()何缓、resume()肢础、stop()

這三個方法已經(jīng)被廢除,通過查看 Oracle 官方文檔 可以得知碌廓。使用 stop() 方法停止線程會釋放線程的所有 monitor传轰,該方法在終止一個線程時不會保證線程的資源正常釋放,并且拋出 ThreadDeath 異常谷婆,通常是沒有給予線程完成資源釋放工作的機會路召,因此會導(dǎo)致程序出現(xiàn)數(shù)據(jù)不同步。suspend() 方法則容易造成死鎖波材,該方法在調(diào)用后股淡,線程不會釋放已經(jīng)占有的資源(比如鎖),而是占有著資源進入掛起狀態(tài)廷区。resume() 必須和 suspend() 一起使用唯灵,當(dāng)要恢復(fù)目標線程的線程在調(diào)用 resume 之前嘗試鎖定這個 monitor,此時就會導(dǎo)致死鎖隙轻。

2.volatile 標志位

通過 volatile 修飾的共享變量可以進行線程的終止埠帕。

2.1 成功案例

子線程每隔 1 秒輸出:持續(xù)運行。主線程在 2 秒后將 stop 置為 true玖绿,此時子線程 while 循環(huán)停止敛瓷,子線程運行結(jié)束。循環(huán)只進行了兩次斑匪。

public class RightVolatileDemo {

    private static volatile boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (!stop) {
                System.out.println("持續(xù)運行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        TimeUnit.SECONDS.sleep(2);
        stop = true;
    }
}

運行結(jié)果如下:

持續(xù)運行
持續(xù)運行

2.3 失敗案例

使用 volatile 的局限性呐籽,當(dāng)線程陷入阻塞時,使用 volatile 修飾的變量無法停止線程。

通過生產(chǎn)者消費者例子來演示阻塞情況下 volatile 的局限性狡蝶,定義一個生產(chǎn)者類實現(xiàn) Runnable 接口重寫 run 方法庶橱,在 run 中當(dāng) volatile 修飾的 canceled 變量為 false 時,生產(chǎn)者通過 BlockingQueue 的 put 方法不斷添加數(shù)據(jù)贪惹,當(dāng)阻塞隊列到達上限時苏章,put 方法會阻塞。定義一個消費者類奏瞬,通過 needMoreCount 方法判斷消費者是否結(jié)束消費枫绅。

在主函數(shù)中初始化一個長度為 10 的阻塞隊列,構(gòu)建生產(chǎn)者和消費者實例硼端,當(dāng)消費者結(jié)束消費時撑瞧,將生產(chǎn)者的 canceled 屬性值改為 true,但是此時生產(chǎn)者仍然在運行显蝌,因為生產(chǎn)者線程阻塞在 put 方法预伺。這就是 volatile 標志位的局限性了。

public class WrongVolatileDemo {

    public static void main(String[] args) throws InterruptedException {
        // 定義容量為 10 的阻塞隊列
        BlockingQueue<Integer> storage = new ArrayBlockingQueue<>(10);

        // 啟動生產(chǎn)者線程
        Thread producerThread = new Thread(new Producer(storage));
        producerThread.start();
        Thread.sleep(1000);

        // 啟動消費者
        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreCount()) {
            System.out.println("消費者消費:" + consumer.getStorage().take());
            Thread.sleep(100);
        }
        System.out.println("消費者消費完全結(jié)束");

        // 此時生產(chǎn)者不應(yīng)該繼續(xù)生產(chǎn)
        Producer.canceled = true;
    }

    /**
     * 生產(chǎn)者
     */
    private static class Producer implements Runnable {

        static volatile boolean canceled = false;
        private BlockingQueue<Integer> storage;

        public Producer(BlockingQueue<Integer> storage) {
            this.storage = storage;
        }

        @Override
        public void run() {
            int count = 1;
            try {
                while (!canceled) {
                    // 如果隊列滿的話曼尊,put 方法會阻塞當(dāng)前線程
                    storage.put(count);
                    System.out.println("生產(chǎn)者生產(chǎn):" + count);
                    count++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("生產(chǎn)者停止運行");
            }
        }
    }

    /**
     * 消費者
     */
    private static class Consumer {

        private BlockingQueue<Integer> storage;

        public Consumer(BlockingQueue<Integer> storage) {
            this.storage = storage;
        }

        public BlockingQueue<Integer> getStorage() {
            return storage;
        }

        public boolean needMoreCount() {
            return Math.random() < 0.95;
        }
    }
}

3.interrupt 方法

interrupt 翻譯為中斷酬诀,中斷可以理解為線程的一個標識位屬性,它表示一個運行中的線程是否被其他線程進行了中斷操作骆撇。中斷好比其他線程對該線程打了個招呼瞒御,其他線程通過調(diào)用該線程的 interrupt() 方法對其進行中斷操作。

舉幾個例子來演示 interrupt 的不同用法神郊。

3.1 不帶阻塞的中斷

該例子是最簡單的中斷肴裙,thread 線程啟動后,休眠 1ms 再調(diào)用該對象的 interrupt 方法涌乳,此時線程中正在執(zhí)行的循環(huán)檢測到 Thread.currentThread().isInterrupted() 為 true 結(jié)束循環(huán)蜻懦,輸出 count 變量的值。

當(dāng)線程調(diào)用自身的 interrupt 方法時夕晓,會將中斷標記設(shè)置為 ture宛乃,線程內(nèi)部循環(huán)會通過檢查自身是否被中斷來結(jié)束循環(huán),而 線程內(nèi)部的 isInterrupted() 方法就能判斷線程是否被中斷蒸辆。

public class InterruptThreadWithoutSleep {
    
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            int count = 0;
            // 檢查自身是否被中斷來結(jié)束循環(huán)
            while (!Thread.currentThread().isInterrupted()) {
                count++;
            }
            System.out.println(count);
        });
        thread.start();
        Thread.sleep(1);
        // 設(shè)置中斷標記
        thread.interrupt();
    }
}

3.2 帶有阻塞的中斷

該例子演示帶有 sleep 阻塞的中斷方法使用征炼。sleep 方法使用需要拋出 InterruptedException,說明該方法可以響應(yīng) interrupt 中斷躬贡。在線程啟動后谆奥,該線程會休眠 1s,而主線程在休眠 100ms 后會調(diào)用中斷方法拂玻,此時該線程是處于阻塞狀態(tài)酸些,在阻塞狀態(tài)下響應(yīng)到中斷宰译,sleep 方法會拋出 InterruptedException ,但是在拋出該異常前擂仍,JVM 會先將該線程的中斷標識位清除,然后才拋出 InterruptedException熬甚,此時調(diào)用 isInterrupted() 方法將會返回 false逢渔。

如果在執(zhí)行過程中,每次循環(huán)都會調(diào)用 sleep 方法乡括,那么其實可以不需要每次迭代都通過 isInterrupted() 方法檢查中斷肃廓,因為 sleep 方法會響應(yīng)中斷。

public class InterruptThreadWithSleep {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("中斷標記:" + Thread.currentThread().isInterrupted());
            }
        });
        thread.start();
        Thread.sleep(100);
        thread.interrupt();
    }
}

運行結(jié)果如下:

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.hncboy.interrupt.InterruptThreadWithSleep.lambda$main$0(InterruptThreadWithSleep.java:15)
    at java.lang.Thread.run(Thread.java:748)
中斷標記:false

3.3 interrupt 相關(guān)方法

3.4.1 interrupt()

設(shè)置中斷標記诲泌,最終調(diào)用 native 的 interrupt0() 方法設(shè)置中斷標記盲赊。

public void interrupt() {
    if (this != Thread.currentThread())
        // 權(quán)限檢查
        checkAccess();
    
    synchronized (blockerLock) {
        // IO 讀寫相關(guān)
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();
            b.interrupt(this);
            return;
        }
    }
    // 該方法一定會執(zhí)行
    interrupt0();
}

private native void interrupt0();

找到 interrupt0 方法對應(yīng)的 JVM_Interrupt 方法,找到該方法代碼敷扫。

JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_Interrupt");

  // Ensure that the C++ Thread and OSThread structures aren't freed before we operate
  oop java_thread = JNIHandles::resolve_non_null(jthread);
  MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock);
  // We need to re-resolve the java_thread, since a GC might have happened during the
  // acquire of the lock
  JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread));
  if (thr != NULL) {
    Thread::interrupt(thr);
  }
JVM_END

找到關(guān)鍵方法 Thread::interrupt 的代碼哀蘑。

void Thread::interrupt(Thread* thread) {
  trace("interrupt", thread);
  debug_only(check_for_dangling_thread_pointer(thread);)
  os::interrupt(thread);
}

找到關(guān)鍵方法 os::interrupt 的代碼,此時找到了設(shè)置中斷標記的方法葵第,Java 中的每個線程都與操作系統(tǒng)的線程一一對應(yīng)绘迁,一個 osthread 就對應(yīng) Java 中的一個線程,如果 osthread 沒有被設(shè)置為中斷卒密,則設(shè)置中斷標記為 true缀台。

void os::interrupt(Thread* thread) {
  assert(Thread::current() == thread || Threads_lock->owned_by_self(),
    "possibility of dangling Thread pointer");

  OSThread* osthread = thread->osthread();
  // 如果線程沒有被中斷 
  if (!osthread->interrupted()) {
    // 設(shè)置中斷標記為 true
    osthread->set_interrupted(true);
    // More than one thread can get here with the same value of osthread,
    // resulting in multiple notifications.  We do, however, want the store
    // to interrupted() to be visible to other threads before we execute unpark().
    OrderAccess::fence();
    ParkEvent * const slp = thread->_SleepEvent ;
    if (slp != NULL) slp->unpark() ;
  }

  // For JSR166. Unpark even if interrupt status already was set
  if (thread->is_Java_thread())
    ((JavaThread*)thread)->parker()->unpark();

  ParkEvent * ev = thread->_ParkEvent ;
  if (ev != NULL) ev->unpark() ;

}

3.4.2 isInterrupted() 和 interrupted()

返回線程的中斷狀態(tài)。interrupted 為靜態(tài)方法哮奇,兩個方法都調(diào)用了 isInterrupted 方法膛腐,不過傳入的參數(shù)不一樣,true 表示清除中斷狀態(tài)鼎俘,false 表示不清除哲身。

Thread.interrupted() 在哪個線程里被調(diào)用,就返回哪個線程的中斷標志贸伐。

public boolean isInterrupted() {
    return isInterrupted(false);
}
  
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

private native boolean isInterrupted(boolean ClearInterrupted);

3.4.2 綜合例子

public class InterruptComprehensive {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {});

        // 啟動線程
        thread.start();
        // 設(shè)置中斷標志
        thread.interrupt();
        // 獲取中斷標志律罢,被中斷了返回 true
        System.out.println("isInterrupted: " + thread.isInterrupted());
        // 獲取中斷標志并重置,interrupted 靜態(tài)方法調(diào)用的是執(zhí)行它的線程棍丐,也就是 main 線程误辑,返回 false
        System.out.println("isInterrupted: " + thread.interrupted());
        // 獲取中斷標志并重置,Main 函數(shù)沒有沒有被中斷歌逢,返回 false
        System.out.println("isInterrupted: " + Thread.interrupted());
        // 獲取中斷標志巾钉,中斷標記沒有被清除,返回 true
        System.out.println("isInterrupted: " + thread.isInterrupted());
        thread.join();
        System.out.println("Main thread is over.");
    }
}

運行結(jié)果:

isInterrupted: true
isInterrupted: false
isInterrupted: false
isInterrupted: true
Main thread is over.

3.4 能響應(yīng)中斷的部分方法

有些阻塞方法是不可中斷的秘案,例如 I/O 阻塞和 synchronized 阻塞砰苍,需要針對某一些鎖或某一些 I/O 給出特定的方案潦匈。

  • Object.wait()/wait(long)/wait(long, int)
  • Thread.sleep(long)/sleep(long, int)
  • Thread.join()/join(long)/join(long, int)
  • java.util.concurrent.BlockingQueue.take()/put(E)
  • java.util.concurrent.locks.Lock.lockInterruptibly()
  • java.util.concurrent.CountDownLatch.await()
  • java.util.concurrent.CyclicBarrier.await()
  • java.util.concurrent.Exchanger.exchange(V)
  • java.nio.channels.InterruptibleChannel 相關(guān)方法
  • java.nio.channels.Selector 相關(guān)方法

3.5 InterruptedException 異常處理

3.5.1 傳遞中斷

當(dāng)在 run 中調(diào)用了一個有異常的方法時,該異常應(yīng)該在方法中用 throws 聲明赚导,傳遞到 run 方法茬缩,而不是在方法中捕獲,此時可能會造成不可預(yù)料的結(jié)果吼旧。throwInMethod2() 為正確做法凰锡,throwInMethod1() 為錯誤做法。

public class HandleInterruptedException implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new HandleInterruptedException());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }

    @Override
    public void run() {
        while (true) {
            System.out.println("work");
            try {
                throwInMethod2();
            } catch (InterruptedException e) {
                System.out.println("保存日志圈暗、停止程序");
                e.printStackTrace();
            }
        }

        /*while (true) {
            System.out.println("go");
            throwInMethod1();
        }*/
    }

    private void throwInMethod2() throws InterruptedException {
        Thread.sleep(2000);
    }

    private void throwInMethod1() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3.5.2 重新設(shè)置中斷狀態(tài)

因為阻塞拋出 InterruptedException 異常后掂为,會清除中斷狀態(tài)≡贝可以在 catch 子語句中調(diào)用 Thread.currentThread().interrupt() 方法來恢復(fù)設(shè)置中斷狀態(tài)勇哗,以便于在后續(xù)的執(zhí)行中,依然能夠檢查到剛才發(fā)生了中斷寸齐。

public class HandleInterruptedException2 implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new HandleInterruptedException2());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }

    @Override
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("發(fā)生中斷欲诺,程序運行結(jié)束");
                break;
            }
            System.out.println("work");
            reInterrupt();
        }
    }

    private void reInterrupt() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}

《Java 并發(fā)編程的藝術(shù)》

深入理解Thread.run()底層實現(xiàn)

Thread.interrupt()相關(guān)源碼分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市渺鹦,隨后出現(xiàn)的幾起案子瞧栗,更是在濱河造成了極大的恐慌,老刑警劉巖海铆,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迹恐,死亡現(xiàn)場離奇詭異,居然都是意外死亡卧斟,警方通過查閱死者的電腦和手機殴边,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來珍语,“玉大人锤岸,你說我怎么就攤上這事“逡遥” “怎么了是偷?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長募逞。 經(jīng)常有香客問我蛋铆,道長,這世上最難降的妖魔是什么放接? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任刺啦,我火速辦了婚禮,結(jié)果婚禮上纠脾,老公的妹妹穿的比我還像新娘玛瘸。我一直安慰自己蜕青,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布糊渊。 她就那樣靜靜地躺著右核,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渺绒。 梳的紋絲不亂的頭發(fā)上贺喝,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音芒篷,去河邊找鬼搜变。 笑死采缚,一個胖子當(dāng)著我的面吹牛针炉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扳抽,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼篡帕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贸呢?” 一聲冷哼從身側(cè)響起镰烧,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎楞陷,沒想到半個月后怔鳖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡固蛾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年结执,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艾凯。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡献幔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趾诗,到底是詐尸還是另有隱情蜡感,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布恃泪,位于F島的核電站郑兴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏贝乎。R本人自食惡果不足惜杈笔,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糕非。 院中可真熱鬧蒙具,春花似錦球榆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至篱昔,卻和暖如春每强,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背州刽。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工空执, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人穗椅。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓辨绊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匹表。 傳聞我的和親對象是個殘疾皇子门坷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

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