如何構造線程
在運行線程之前需要先構造線程對象,線程對象的構造需要指定線程所需要的屬性,比如:所屬線程組、線程優先級、是否為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對象的屬性如下:
如何使用線程
實現多線程編程的方式主要有兩種:一種是繼承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() 方法 帶來的副作用, 這些方法才被標志位不建議使用的過期方法, 而暫停和恢復可以使用 等待/ 通知機制 來替代。