天天看點

Java并發程式設計的藝術-----Java并發程式設計基礎(啟動和終止線程)一、構造線程二、啟動線程三、了解中斷四、過期的suspend()、resume()和stop()五、安全地終止線程

Java并發程式設計基礎(啟動和終止線程)

  • 一、構造線程
  • 二、啟動線程
  • 三、了解中斷
  • 四、過期的suspend()、resume()和stop()
  • 五、安全地終止線程

一、構造線程

在運作線程之前首先要建構一個線程,線程對象在構造的時候需要提供線程所需要的屬性,如線程所屬的線程組、線程優先級、是否是Daemon線程等資訊。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();

        /*
         * Do we have the required permissions?
         */
        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);
        //将父線程的inheritableThreadLocal複制過來
        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 */
        //配置設定一個線程ID
        tid = nextThreadID();
    }
           

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

二、啟動線程

線程對象在初始化完成之後,調用start()方法就可以啟動這個線程。線程start()方法的含義是:目前線程(即parent線程)同步告知Java虛拟機,隻要線程規劃器空閑,應立即啟動調用start()方法的線程。

三、了解中斷

中斷可以了解為線程的一個辨別位屬性,它表示一個運作中的線程是否被其他線程進行了中斷操作。其他線程通過調用該線程的interrupt()方法對其進行中斷操作。

線程通過檢查自身是否被中斷來進行響應,線程通過方法isInterrupted()來進行判斷是否被中斷,也可以調用靜态方法Thread.interrupted()對目前線程的中斷辨別位進行複位。如果該線程已經處于終結狀态,即使該線程被中斷過,在調用該線程對象的isInterrupted()時依舊傳回false。

從JavaAPI中可以看出,許多聲明抛出InterruptedException的方法在抛出InterruptedException之前,Java虛拟機會先将該線程的中斷辨別位清除,然後抛出InterruptedException,此時調用isInterrupted()将會傳回false。示例代碼如下:

package com.wholesmart.thread4;

import java.util.concurrent.TimeUnit;

public class Interrupted {
	public static void main(String[] args) throws Exception {
		// 不停嘗試休眠
		Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
		sleepThread.setDaemon(true);
		// 不停運作
		Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
		busyThread.setDaemon(true);
		sleepThread.start();
		busyThread.start();
		// 休眠5秒,讓sleepThread和busyThread充分運作
		TimeUnit.SECONDS.sleep(5);
		sleepThread.interrupt();
		busyThread.interrupt();
		System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
		System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
		// 防止sleepThread和busyThread立刻退出
		SleepUtils.second(2);
	}

	/**
	 * 一直嘗試休眠的線程
	 * 
	 * @author dyw
	 * @date 2020年7月14日
	 */
	static class SleepRunner implements Runnable {

		@Override
		public void run() {
			while (true) {
				SleepUtils.second(10);
			}
		}
	}

	/**
	 * 忙碌的線程
	 * 
	 * @author dyw
	 * @date 2020年7月14日
	 */
	static class BusyRunner implements Runnable {

		@Override
		public void run() {
			while (true) {

			}
		}
	}
}
           

運作輸出結果如下

SleepThread interrupted is false
BusyThread interrupted is true
           

從結果可以看出,抛出InterruptedException的線程SleepThread,其中斷辨別位被清除了,而一直運作的線程BusyThread,中斷辨別位沒有被清除。

四、過期的suspend()、resume()和stop()

類比于CD機,如果把播放音樂作為一個線程的運作,那麼對音樂的播放做出的暫停、恢複和停止操作對應線程Thread的API就是suspend()、resume()和stop()。示例代碼如下:

package com.wholesmart.thread4;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class Deprecated {
	public static void main(String[] args) throws Exception {
		DateFormat format = new SimpleDateFormat("HH:mm:ss");
		Thread printThread = new Thread(new Runner(), "PrintThread");
		printThread.setDaemon(true);
		printThread.start();
		TimeUnit.SECONDS.sleep(3);
		//暫停輸出
		printThread.suspend();
		System.out.println("main suspend PrintThread at " + format.format(new Date()));
		TimeUnit.SECONDS.sleep(3);
		//恢複輸出
		printThread.resume();
		System.out.println("main resume PrintThread at " + format.format(new Date()));
		TimeUnit.SECONDS.sleep(3);
		//結束輸出
		printThread.stop();
		System.out.println("main stop PrintThread at " + format.format(new Date()));
		TimeUnit.SECONDS.sleep(3);
	}

	static class Runner implements Runnable {

		@Override
		public void run() {
			DateFormat format = new SimpleDateFormat("HH:mm:ss");
			while (true) {
				System.out.println(Thread.currentThread().getName() + "Run at " + format.format(new Date()));
				SleepUtils.second(1);
			}
		}
	}
}
           

輸出示例如下(可能會不同):

PrintThreadRun at 23:20:50
PrintThreadRun at 23:20:51
PrintThreadRun at 23:20:52
PrintThreadRun at 23:20:53
main suspend PrintThread at 23:20:53
PrintThreadRun at 23:20:56
main resume PrintThread at 23:20:56
PrintThreadRun at 23:20:57
PrintThreadRun at 23:20:58
main stop PrintThread at 23:20:59
           

在執行過程中PrintThread運作3秒,随後被暫停3秒,最後經過3秒被停止。但是這些API是過期的,不建議使用的原因如下:

Java并發程式設計的藝術-----Java并發程式設計基礎(啟動和終止線程)一、構造線程二、啟動線程三、了解中斷四、過期的suspend()、resume()和stop()五、安全地終止線程

五、安全地終止線程

中斷狀态是線程的一個辨別位,而中斷操作是一種簡便的線程交方式,而這種互動方式最适合用來取消或是停止任務。除了中斷以外,還可以利用一個boolean變量來控制是否需要停止任務并終止該線程。

下面的示例,建立一個線程CountThread,它不斷地進行變量的累加,而主線程嘗試對其進行中斷操作和停止操作:

package com.wholesmart.thread4;

import java.util.concurrent.TimeUnit;

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;
		}
	}
}
           

輸出結果如下(會有不同):

Count i = 600573650
Count i = 527952155
           

這種通過辨別位或者中斷操作的方式能夠使得線程在終止時有機會去清除資源,而不是武斷的将線程停止,是以這種終止線程的做法顯得更見安全和優雅。

附SleepUtils 源碼:

package com.wholesmart.thread4;

import java.util.concurrent.TimeUnit;

public class SleepUtils {
	public static final void second(long senconds) {
		try {
			TimeUnit.SECONDS.sleep(senconds);
		} catch (InterruptedException e) {

		}
	}
}
           

最明目張膽的恭維,就是把“悍婦撒潑”說成是“貴妃醉酒”。

繼續閱讀