Java線程的啟動和停止(一)


如何構造線程

在運行線程之前需要先構造線程對象,線程對象的構造需要指定線程所需要的屬性,比如:所屬線程組、線程優先級、是否為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 線程。 至此, 一個 能夠 運行 的 線程 對象 就 初始化 好了, 在 堆 內存 中 等待 着 運行。

我們構造一個Thread thread=new Thread(),打上斷點,可以看到thread對象的屬性如下:
image

如何使用線程

實現多線程編程的方式主要有兩種:一種是繼承Thread類,一種是實現Runnable接口,使用繼承Thread類最大的局限性是不支持多繼承,由於java單繼承的特性,為了突破單繼承的限制,於是就有了另一個實現方式,就是實現Runnable接口。使用這兩種方式創建的線程工作性質是一樣的,沒有本質的區別。

繼承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");
    }
}

如何啟動線程

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

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

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

如何停止線程

如何判斷線程是否停止

在介紹如何停止線程的知識點前, 先看一下如何判斷線程是否停止的狀態。 Thread.java類中提供了兩個很重要的方法:

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

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

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

 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

下面我們接着查看 isInterrupted()方法:

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():判斷線程終止的狀態,不具有清除狀態標志的功能

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 我被停止了,退出循環

過期的 suspend()、 resume() 和 stop()方法終止線程

suspend()在調用后線程不會釋放已經占有的資源,二是占有着資源進入睡眠狀態,這樣容易引發死鎖問題。同樣的stop()方法在終止一個線程時不會保證線程的資源正常釋放,通常線程沒有機會去釋放資源,因此會導致程序工作狀態的不確定性。

正是因為 suspend()、 resume() 和 stop() 方法 帶來的副作用, 這些方法才被標志位不建議使用的過期方法, 而暫停和恢復可以使用 等待/ 通知機制 來替代。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM