天天看點

多線程并發學習(三):啟動和終止線程

1. 構造線程

    運作線程之前需要先構造線程對象,構造線程對象時需指定線程所需要的屬性,比如:所屬線程組、線程優先級、是否為daemon線程等資訊。

   java.lang.Thread中對線程初始化的方法如下:

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;
        //目前線程就是該線程的父線程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * 是否有通路權限
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();
        //線程組
        this.group = g;
        //使用父線程的daemon、priority屬性
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* 配置設定一個線程ID */
        tid = nextThreadID();
    }

           

          一個 新 構造的 線程對象 是由其 parent 線程來進行空間 配置設定 的, 而child線程繼承了 parent 是否為 Daemon、優先級和加載資源的 contextClassLoader 以及可繼承的 ThreadLocal, 同時還會配置設定一個唯一的ID 來辨別這個child線程。至此,一個能夠運作的線程對象就初始化好了, 在堆記憶體中等待着運作。

2. 實作多線程

    實作多線程程式設計的方式主要有兩種:

   一種繼承Thread類,

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("我是自定義線程MyThread");
    }
}
           

   一種實作Runnable接口。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我是Runnable");
    }
}
           

    使用繼承Thread類最大的局限性是不支援多繼承,由于java單繼承的特性,為了突破單繼承的限制,于是就有了另一個實作方式,就是實作Runnable接口。使用這兩種方式建立的線程工作性質是一樣的,沒有本質的差別。

3. 啟動線程

        線程對象初始化完成後,調用線程的start()方法就可以啟動線程了,start()方法是告訴Java虛拟機,如果線程規劃期空閑,應該立即啟動調用了start()方法的線程。

        同一個線程不能多次 調用 start() 方法, 否則會出現異常 Exception in thread" main" java. lang. IllegalThreadStateException。

         線程的start()方法,會新啟動一個線程,而線程run()方法則是同步等待目前線程調用。

4.判斷線程是否停止狀态

        中斷可以了解為線程的一個辨別位屬性,它表示一個運作中的線程是否被其他線程進行了中斷操作。

      1) this.interrupted(): 判斷目前線程是否已經中斷

      2) this.isInterrupted(): 判斷線程是否已經中斷

那麼這兩種方法的差別是什麼呢? 先來看看 this.interrupted()方法: 判斷目前線程是否已經中斷,目前線程是指運作 this. interrupted()方法 的 線程。

  1. /**
        * Tests whether the current thread has been interrupted.  The
        * * <i>interrupted status</i> of the thread is cleared by this method.  In
         * other words, if this method were to be called twice in succession, the
         * second call would return false (unless the current thread were
         * interrupted again, after the first call had cleared its interrupted
         * status and before the second call had examined it).
         *
         * <p>A thread interruption ignored because a thread was not alive
         * at the time of the interrupt will be reflected by this method
         * returning false.
         *
         * @return  <code>true</code> if the current thread has been interrupted;
         *          <code>false</code> otherwise.
         * @see #isInterrupted()
         * @revised 6.0
         */
         // 驗證目前線程是否被中斷過。對目前線程的中斷辨別位進行複位。換句話說,如果連續兩次調用該方法,第二次調用時會傳回false(除非是調用第一次後,調用第二次之前的時間空擋内又被中斷過,會傳回true)
        public static boolean interrupted() {
            return currentThread().isInterrupted(true);
        }
               
    public static void main(String[] args) {
            Thread.currentThread().interrupt();
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " 目前線程是否已停止:=" + Thread.interrupted());
            System.out.println(threadName + " 目前線程是否已停止:=" + Thread.interrupted());
    
        }
    main 目前線程是否已停止:=true
    main 目前線程是否已停止:=false
               
    從執行結果可知 interrupt()方法确實停止了線程,但是第二個判斷結果為什麼是false呢,通過檢視官方文檔當解釋得知。interrupted() 具有清除狀态的功能,是以第二個判斷結果為false
public static void main(String[] args) {
        Thread.currentThread().interrupt();
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " 目前線程是否已停止:=" + Thread.currentThread().isInterrupted());
        System.out.println(threadName + " 目前線程是否已停止:=" + Thread.currentThread().isInterrupted());

    }
    
main 目前線程是否已停止:=true
main 目前線程是否已停止:=true
           

輸出的結果都為true,isInterrupted()并未清除狀态标志,最後我們得出如下結論:

1) this.interrupted(): 判斷線程終止的狀态, 執行後具有将狀态标志置為false的功能

2) this.isInterrupted():判斷線程終止的狀态,不具有清除狀态标志的功能

5.  線程終止

     java中有3種方式可以終止正在運作的線程

    ①線程正常退出,即run()方法執行完畢了

    ②使用Thread類中的stop()方法強行終止線程。但stop()方法已經過期了,不推薦使用。

過期的 suspend()、 resume() 和 stop()方法終止線程:suspend()在調用後線程不會釋放已經占有的資源,二是占有着資源進入睡眠狀态,這樣容易引發死鎖問題。同樣的stop()方法在終止一個線程時不會保證線程的資源正常釋放,通常線程沒有機會去釋放資源,是以會導緻程式工作狀态的不确定性。正是因為 suspend()、 resume() 和 stop() 方法 帶來的副作用, 這些方法才被标志為不建議使用的過期方法, 而暫停和恢複可以使用 等待/ 通知機制 來替代。

    ③使用中斷機制

try…catch與this.interrupt方法終止線程

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5000; i++) {
            if (interrupted()) {
                System.out.println(Thread.currentThread().getName() + " 我被停止了,退出循環");
                break;
            }
            System.out.println(" i=" + (i + 1));
        }
    }
}

public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.setName("Thread-interrupt-0");
        thread.start();
        try {
            Thread.sleep(1);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(" 結束!");
    }
    
輸出結果:
.....
i=32
 i=33
 i=34
 i=35
 i=36
 i=37
 i=38
 i=39
 i=40
 i=41
 結束!
Thread-interrupt-0 我被停止了,退出循環
           

        如何安全的終止線程:

        1).中斷操作方式-interrupt()

        2).自定義辨別位

public class Shutdown {

    public static void main(String[] args) throws Exception {
        Runner one = new Runner();
        Thread countThread = new Thread(one, "CountThread");
        countThread.start();
        // 睡眠1秒,main線程對CountThread進行中斷,使CountThread能夠感覺中斷而結束
        TimeUnit.SECONDS.sleep(1);
        countThread.interrupt();
        Runner two = new Runner();
        countThread = new Thread(two, "CountThread");
        countThread.start();
        // 睡眠1秒,main線程對Runner two進行取消,使CountThread能夠感覺on為false而結束
        TimeUnit.SECONDS.sleep(1);
        two.cancel();
    }
    private static class Runner implements Runnable {
        private long i;
        private volatile boolean on = true;
        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()){
                i++;
            }
            System.out.println("Count i = " + i);
        }
        public void cancel() {
            on = false;
        }
    }
}
           

        示例在執行過程中,main線程通過中斷操作(interrupt()方法)和cancel()方法均可使CountThread得以終止。這種通過辨別位或者中斷操作的方式能夠使線程在終止時有機會去清理資源,而不是武斷地将線程停止,是以這種終止線程的做法顯得更加安全和優雅。

原文:https://blog.csdn.net/u010647035/article/details/82317134 

繼續閱讀