執行緒的基本操作:新建和終止執行緒

takeOffOnTime發表於2020-10-05

執行緒的基本操作

新建執行緒

Java官方只在文件中宣告瞭兩種建立執行緒的方式,一種是繼承Thread類,一種是實現Runnable介面。
至於其他方式,Callable ,執行緒池,Future等建立執行緒的方式底層還是通過這兩種方式實現的。

  • 可以用Thread類建立一個新執行緒。
  • 然後呼叫該執行緒的start方法。
class MyThread extends Thread{
	//重寫run方法
	public void run() {
       //....
    }
}
public static void main(String[]args)
    {
      Thread thread = new MyThread();//執行緒狀態為NEW
      thread.start();//通知執行緒可以啟動,當執行緒真正獲得CPU時,狀態轉為RUNNABLE
    }
  • 使用Runnable 介面建立一個執行緒
  • 然後呼叫該執行緒的start方法。
	    public static void main(String[]args)
    {
      Thread thread = new Thread(new Runnable() {
          @Override
          public void run() {
              //...邏輯程式碼
          }
      });
      thread.start();
    }

Thread類有關初始化執行緒和start方法部分原始碼:
在new了一個Thread類之後,在空參構造方法中會呼叫一個私有的方法init,然後他有一個過載的方法,init方法主要是設定一些執行緒的基本資訊的,包括所屬執行緒組,執行緒名字,是否是守護執行緒,執行緒的優先順序等。
如果不在new時傳遞執行緒的名字,會預設賦值一個執行緒名"Thread-" + nextThreadNum();
這裡的nextThreadNum方法是一個同步方法,它可以保證建立出來的每個執行緒都有不同的名字。內部其實只做了一個整數的++操作。

run()方法的實現的呼叫方式與是否實現Runnable介面有關係。

start()方法可以看到只有當threadStatus == 0才會執行成功,否則會拋IllegalThreadStateException,這裡的threadStatus就是之前寫過的用來儲存執行緒狀態的成員,至於當他為0時狀態是什麼,我們可以看看列舉類State中定義了一個方法getState(),然後內部呼叫了sun.misc.VM.toThreadState(threadStatus);方法,跟進這個方法,發現threadStatus==0時表示的是NEW狀態。所以說一個執行緒只能呼叫一次start()方法,而且只能是NEW狀態時呼叫。start方法也是一個同步方法,他不知道通過synchronized關鍵字來保證執行緒安全,內部還設定了一個標誌位started 來保證執行緒真正啟動時的安全。至於為什麼呼叫start方法後會執行重寫的run方法,對應的程式碼邏輯應該寫在了本地方法start0中,說到本地方法,Thread類中也為本地方法提供了註冊方法registerNatives(),這個方法也在Object類中出現過。

關於執行緒的優先順序通過執行緒內部的一個私有變數priority設定,可以通過setPriority(int) 來設定,但是通過設定執行緒的優先順序並不能保證執行緒執行的正確性,因為雖然在java中設定了多執行緒的優先順序。但是有的作業系統會忽略掉Java程式中所設定的優先順序。比如說Mac OS 10.10就將所有的Java程式中的執行緒的優先順序都設定為5。所以說有時候雖然設定了優先順序,但是執行緒真正的執行順序並不會按照所設定優先順序的順序來執行。

class Thread implements Runnable {
 	private volatile String name;
    private int            priority;//執行緒的優先順序。預設是5,範圍是1-10
    //空參構造方式
	public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    //空參建構函式
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    //實現Runnable介面呼叫的建構函式。
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
	/*  run方法內部實現,繼承Thread類需要重寫,
		實現Runnable介面呼叫的還是Runnable介面的實現類的閏方法
	*/
	public void run() {
        if (target != null) {
            target.run();
        }
    }
	

    //保證執行緒名字不同
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
    //初始化執行緒
    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();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        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;

        /* Set thread ID */
        tid = nextThreadID();
    }
}

    public synchronized void start() {
   
        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) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    private native void start0();
	public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }
  • 面試問題:呼叫start()方法和直接呼叫run()方法有什麼不同?
    • 首先看一下JDK文件中關於start的介紹:
      public void start​()導致此執行緒開始執行; Java虛擬機器呼叫此執行緒的run方法。
      結果是兩個執行緒同時執行:當前執行緒(從呼叫返回到start方法)和另一個執行緒(執行其run方法)。

      不止一次啟動執行緒是不合法的。 特別地,一旦執行緒完成執行,可能不會重啟執行緒。

      異常
      IllegalThreadStateException - 如果執行緒已經啟動。

    • 看一下run()方法的文件
      如果這個執行緒使用單獨的Runnable執行物件構造,那麼這個Runnable物件的run方法被呼叫; 否則,此方法不執行任何操作並返回。
      Thread子類應該覆蓋此方法。

  1. 在官方文件中我們可以看出來首先第一點不同是,start方法呼叫後會真正意義上建立出一個執行緒並在記憶體中執行,而呼叫run方法就像是呼叫普通方法一樣,執行緒數量不會發生改變,並不會建立一個新的執行緒。
  2. 呼叫次數的限制,start方法只能呼叫一次,而run方法可以呼叫多次。這種區別主要是因為執行緒狀態的原因。要想start方法能夠正確執行,那執行緒的狀態必須是NEW狀態,而呼叫了start方法之後,執行緒會變為就緒態,只有當該新建的執行緒真正獲得CPU時執行緒狀態才會轉為RUNNABLE,此時threadStatus!=0了,所以如果再次呼叫start方法會報錯。而對於run方法則不會使執行緒的狀態發生改變,呼叫時也沒有執行緒狀態必須是NEW的要求。只有當run方法執行結束或者呼叫終止執行緒方法或執行緒異常終止時,執行緒的狀態會變為TERMINATED。

終止執行緒

首先小夥伴們可能想到stop()方法,大家也都知道stop()方法已經過時了,並且在未來Java可能會移除掉這個方法。
主要是因為這個方法太過暴力,它不會管執行緒是否執行完成就強行終止執行緒,而且會釋放掉該執行緒所持有的鎖,而鎖正是保持資料一致性所需要的。這就有可能導致資料寫到一半,執行緒終止後其他執行緒就會讀到這個被寫壞的資料。
如圖:
在這裡插入圖片描述
那麼如何安全的終止一個執行緒呢?可以通過一個標誌位來進行終止執行緒或者使用中斷來停止執行緒。
對於使用一個標誌位來實現執行緒終止程式碼實現如下:
通過自己設定標誌位或者中斷的方式停止執行緒可以有效的解決資料不一致問題和合理的清理資源。

	public class MyThread extends Thread{
		private volatile boolean stopMe = false;
		
		public void stopMe(){
			stopMe = true;
		}
		@Override
		public void run(){
			while(stopMe && !Thread.currentThread.isInterrupted()){
			//...邏輯
			}
			
		}
	}

相關文章