多線程
一、理解線程和進程
1、進程:
1.一個正在執行的程序
2.進程是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位
2、線程:
1.就是進程中的一個獨立的控制單元。線程在控制着進程的執行。只要進程中有一個線程在執行,進程就不會結束。
2.一個程序至少有一個進程,一個進程至少有一個線程
3、區別:
1.線程的改變只代表了 CPU 執行過程的改變,而沒有發生進程所擁有的資源變化。出了 CPU 之外,計算機內的軟硬件資源的分配與線程無關。
2.進程擁有一個完整的虛擬地址空間,不依賴於線程而獨立存在;反之,線程是進程的一部分,沒有自己的地址空間,與進程內的其他線程一起共享分配給該進程的所有資源。
4、多線程:
在java虛擬機啓動的時候會有一個java.exe的執行程序,也就是一個進程。
1.該進程中至少有一個線程負責java程序的執行。而且這個線程運行的代碼存在於main方法中。該線程稱之爲主線程。
2.JVM啓動除了執行一個主線程,還有負責垃圾回收機制的線程。
3.用戶自建的一些線程。
像這種一個進程裏面有多個線程執行的方式,就叫做多線程。
看到了3個進程(垃圾回收不考慮了),main線程和Thread-0和Thread-1線程。
5、多線程存在的意義
目的:
創建線程的目的是爲了開啓一條執行路徑,去運行指定代碼或者其他代碼已實現同步運行。
優點:
1.提高效率:並行執行的效率絕大多情況下要好於串行執行。
2.佔用大量處理時間的任務可以定期將處理器時間讓給其它任務
3.可以隨時停止任務
4.可以分別設置各個任務的優先級以優化性能
弊端:
1.線程是阻塞式的,資源消耗大
2.上下文切換的開銷,即從一個線程切換到另外一個線程的開銷
3.使用不當,會出現死鎖,這是一種邏輯上的錯誤
4.對共享資源的處理方式會變得麻煩,涉及同步問題
但總體上,多線程的優點是很大的。
6.電腦CPU的多線程運行原理
1.cpu的運行速度很快,如果是單核cpu的話,那麼它會採用一種時分複用的方式,以響應速度極快的方式切換線程,使你覺得是多線程操作。
2.多核cpu可以實現真正意義上的多線程,可以使多個核心分別執行一個線程。
cpu的執行是無規律的,哪個線程被cpu執行,或者說搶到了cpu的執行權,哪個線程就執行。而cpu不會只執行一個,當執行一個一會後,又會去執行另一個,或者說另一個搶走了cpu的執行權。至於究竟是怎麼樣執行的,只能由cpu決定。
二、創建線程的方式
繼承Thread類和實現Runnable接口
1.繼承方式
1.創建類繼承Thread類;
2.覆蓋run()方法;
3.創建子類對象;
4.調用start()方法。
重點在於run()方法的覆蓋,run()方法中存儲的就是線程要執行的代碼。
start()方法會以線程的方式調用run()方法,與用戶直接調用run()方法不同,通過start()調用run()方法纔可以使run()中程序爲線程性的。
public class Demo1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
new Test1("小強").start();
new Test2("xiaowang").start();
for(int i=0;i<5;i++){
System.out.println(i+".......線程是:"+Thread.currentThread().getName());
}
}
}
class Test1 extends Thread{
private String name;
public Test1(String name){
this.name = name;
}
public void run(){
for(int i=0;i<5;i++){
for(int j=1;j<9999;j++){}
System.out.println("i="+i+"........線程是"+Thread.currentThread().getName());
}
}
}
class Test2 extends Thread{
private String name;
public Test2(String name){
this.name = name;
}
public void run(){
for(int j=0;j<5;j++){
for(int i=1;i<9999;i++){}
System.out.println("j="+j+"........線程是"+Thread.currentThread().getName());
}
}
}
注意兩個方法:
1.Thread類的構造方法,將會自動構建這個線程的名字,形如:Thread-數字。
而他的名字是可以改變的,通過子類的構造函數調用super(String name);會把線程的名字改寫爲用戶自定義的。
2.Thread.currentThread(),獲取當前進程對象的方法。
2.接口實現方式
優點:使用接口,避免了創建了一個龐大的線程體系類,只需要在需要使用線程的時候實現Runnable接口就可以了,而且只需要關心run()方法的覆蓋就可以了,避免了繼承會帶來許多不需要的Thread類中的方法。
實現步驟:
1.創建類實現Runnable接口
2.實現Run()方法,將線程要運行的代碼存放在該run()方法中
3.主函數中創建Runnable的實例
4.通過Thread(Runnable target)構造方法,將Runnable接口的子類對象作爲實參傳遞給Thread類的構造方法。
5.調用Thread類中start()方法啓動線程。start方法會自動調用Runnable接口子類的run方法。
/***
* 賣票功能
* @author LQX
*實現Runnable接口
*/
public class Demo2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket num = new Ticket();
for(int i = 1;i<4;i++){
Thread t = new Thread(num);
t.start();
}
}
}
class Ticket implements Runnable{
private int num = 500;
@Override
public void run() {
while(true){
if(num>0){
System.out.println("線程..."+Thread.currentThread().getName()+"正在賣票,剩餘票....."+num--);
}
}
}
}
3.兩種方式的不同
通過覆蓋Thread中的run()方法和實現Runnable接口中的run()方法的不同?
查看源碼:
public void run() {
if (target != null) {
target.run();
}
}
這是本來的Thread中的run()方法,target是Runnable的子類對象
採用繼承Thread重寫run()方法,那麼該函數被覆蓋,然後對象調用start()方法,start()方法調用run();
採用實現Runnable接口有所不同,傳遞了參數Runnable 子類實例,if判斷爲真,執行Runnable.run()方法,其實就是通過Thread.run()調用Runnable.run(),後由Thread.start()調用Thread.run()。
4.線程的幾種狀態
被創建:等待啓動,調用start啓動。
運行狀態:具有執行資格和執行權。
臨時狀態(阻塞):有執行資格,但是沒有執行權。
凍結狀態:遇到sleep(time)方法和wait()方法時,失去執行資格和執行權,sleep方法時間到或者調用notify()方法時,獲得執行資格,變爲臨時狀態。
消忙狀態:stop()方法,或者run方法結束。
注:當已經從創建狀態到了運行狀態,再次調用start()方法時,就失去意義了,java運行時會提示線程狀態異常。
5.線程安全問題
一個線程的run()方法還沒有執行完畢,另一個線程也在執行run()方法。那麼他們共享的數據將會出現問題。
注:線程安全問題在理想狀態下,不容易出現,但一旦出現對軟件的影響是非常大的。
比如:
public class Demo2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket num = new Ticket();
for(int i = 1;i<4;i++){
Thread t = new Thread(num);
t.start();
}
}
}
class Ticket implements Runnable{
private int num =5;
@Override
public void run() {
while(true){
//標註1
if(num>0){
for(int j=0;j<999999;j++){}
System.out.println("線程..."+Thread.currentThread().getName()+"正在賣票,剩餘票....."+num--);
}
}
}
}
以上的代碼,我在運行時,出現了-1
分析:
主要就是標註1的問題,在run()方法中,執行1條語句不會發生安全性問題,當有兩條及以上語句時,可能會出現。
上面的結果中可以看出,num=1時,Thread-0,Thread-1,Thread-2同時進行了num>0判斷,都執行了打印,但卻由於爲共享數據的問題,使得程序沒有正確進行。
解決辦法:
要是每次操作共享數據時,可以只讓一個線程操作,待執行完畢後,返回
數據後,在進行下一次的線程的操作。那麼,就需要synchronized(同步)關鍵字了。
三、synchronized(同步)
java提供關鍵字synchronized來解決線程不安全的問題。
使用方式:
一種是同步代碼塊,
二就是同步函數。都是利用關鍵字synchronized來實現。
1.同步代碼塊:
synchronized(對象)
{需要被同步的代碼}
此處的對象,好比一把鎖,線程進去,需要看這個鎖是否是true,可以,就進去,並置鎖爲false,若此期間,有其他線程想要進來,那麼,不好意思,鎖是false,請等待吧。當一個線程執行完畢後,再把鎖置爲true,那麼下一個線程纔可以進去。
/***
* 賣票功能
* @author LQX
*實現Runnable接口,並實現賣票的同步
*/
public class Demo2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket num = new Ticket();
for(int i = 1;i<4;i++){
Thread t = new Thread(num);
t.start();
}
}
}
class Ticket implements Runnable{
private int num =50;
private Object obj = new Object();
@Override
public void run() {
while(true){
//給程序加同步,即鎖
synchronized (obj) {
if(num>0){
try {
//使用線程中的sleep方法,模擬線程出現的安全問題
//因爲sleep方法有異常聲明,所以這裏要對其進行處理
//注意:不可以向上拋出,因爲Runnable接口並沒有拋異常,子類也不可以拋
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程..."+Thread.currentThread().getName()+"正在賣票,剩餘票....."+num--);
}
}
}
}
}
同步後的代碼,使用起來非常好用,多個線程對他進行同時操作,他不會出現,同時對共享數據操作,數據的輸出或者返回有誤的問題了。
2.同步函數
格式:
在函數上加上synchronized修飾符即可。
那麼同步函數用的是哪一個鎖呢?
函數需要被對象調用。那麼函數都有一個所屬對象引用。就是this。所以同步函數使用的鎖是this。
public class Demo2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket num = new Ticket();
for(int i = 1;i<4;i++){
Thread t = new Thread(num);
t.start();
}
}
}
class Ticket implements Runnable{
private int num =50;
private Object obj = new Object();
@Override
public void run() {
while(true){
show();
}
}
//直接在函數上用synchronized修飾即可實現同步
public synchronized void show(){
if(num>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程..."+Thread.currentThread().getName()+"正在賣票,剩餘票....."+num--);
}
}
}
同步的前提:
a.需要同步的代碼至少要有2句,否則,不會出現安全問題。
b.必須要有兩個或者兩個以上的線程。
c.必須是多個線程使用同一個鎖。
如何尋找多線程中的安全問題?
a.明確哪些代碼是多線程操作的
b.明確哪些數據是共享的
c.明確多線程運行代碼塊中哪些語句是操作共享數據的。
單例設計模式-懶加載涉及到的安全問題
先來看一個例子:
/**
* 單例設計模式
* @author LQX
*採用懶加載
*/
public class Single {
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s==null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
s = new Single();
}
return s;
}
public static void main(String[] args) {
Demo d1 = new Demo();
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d1);
Thread t3 = new Thread(d1);
Thread t4 = new Thread(d1);
Thread t5 = new Thread(d1);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class Demo implements Runnable{
public void run() {
Single s1 = Single.getInstance();
System.out.println(s1);
}
}
結果,每個線程取到的不是唯一的對象,說明,存在線程安全問題。
解決,加同步代碼塊或使用同步函數,這裏使用同步代碼塊,因爲這裏還有一個效率問題。
/**
* 單例設計模式
* 採用懶加載(並使用了安全機制)
*/
public class Single {
private static Single s = null;
private Single() {
}
public static Single getInstance() {
// 這裏進行判斷,第一次若沒有new的話,則進入,以後若已經存在了對象,則不必在進去,效率問題
if (s == null) {
synchronized (Single.class) {
if (s == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
s = new Single();
}
}
}
return s;
}
public static void main(String[] args) {
Demo d1 = new Demo();
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d1);
Thread t3 = new Thread(d1);
Thread t4 = new Thread(d1);
Thread t5 = new Thread(d1);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class Demo implements Runnable {
public void run() {
Single s1 = Single.getInstance();
System.out.println(s1);
}
}
可以看到,現在取出來是同一個對象,不存在線程不安全的問題了。
靜態同步函數使用:
靜態的同步函數使用的鎖是該函數所屬字節碼文件對象,可以用getClass方法獲取,也可以用當前類名.class表示。
public class StaticThread {
public static void main(String[] args) {
Ticket1 t = new Ticket1();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.flag = false;
t2.start();
}
}
class Ticket1 implements Runnable{
private static int num = 100;
boolean flag = true;
public void run(){
if(flag){
//需要同步,同步代碼塊
while(true){
synchronized (this.getClass()) {
if(num>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+".....runnable...."+num--);
}
}
}
}else{
//這裏面已經同步過了
while(true){
show();
}
}
}
public synchronized static void show(){
if(num>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+".....fuction...."+num--);
}
}
}
死鎖示例
/***
* 寫一個死鎖
* 鎖的嵌套就會導致死鎖
*/
public class DeadLock {
public static void main(String[] args) {
DeadlockDemo d1 = new DeadlockDemo(true);
DeadlockDemo d2 = new DeadlockDemo(false);
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
t1.start();
t2.start();
}
}
class DeadlockDemo implements Runnable {
public static Object locka = new Object();
public static Object lockb = new Object();
private boolean flag;
public DeadlockDemo(boolean f) {
this.flag = f;
}
public void run() {
if (flag) {
while (true) {
synchronized (locka) {
System.out.println(Thread.currentThread().getName() + "......if locka...");
synchronized (lockb) {
System.out.println(Thread.currentThread().getName() + "......if lockb...");
}
}
}
} else {
while (true) {
synchronized (lockb) {
System.out.println(Thread.currentThread().getName() + "......else lockb...");
synchronized (locka) {
System.out.println(Thread.currentThread().getName() + "......else locka...");
}
}
}
}
}
}
死鎖的形成:線程1的運行需要兩個鎖a和b,而且有順序,要先獲得a鎖纔可以獲得b鎖;線程2與1相反,那麼,線程1獲得a鎖,線程2獲得b鎖,兩邊都在等待對方先釋放鎖,那麼就會處於無限等待的狀態,就是死鎖了。
4.線程間的通信
上面講的同步問題是建立在對同一操作下的不同線程,那麼只要加上synchronized關鍵字就可以解決了。當操作共享資源的是不同的操作,或者說,只是操作同一對象,而操作是完全不同的類時,那麼,線程間應該如何保證同步問題。
那就需要用到線程間的通信了:
等待/喚醒機制。
涉及的方法:
1,wait(): 讓線程處於凍結狀態,被wait的線程會被存儲到線程池中。
2,notify():喚醒線程池中一個線程(任意).
3,notifyAll():喚醒線程池中的所有線程。
先寫一個簡單的例子,生產米飯,做一份米飯,就消費一份米飯。那麼只有一個生產者和消費者。
/***
* 多線程的生產者和消費者
*
*/
public class CustomerProducerDemo1 {
public static void main(String[] args) {
Production p = new Production();
Producer pro1 = new Producer(p);
Customer cus1 = new Customer(p);
Thread t1 = new Thread(pro1);
Thread t3 = new Thread(cus1);
t1.start();
t3.start();
}
}
class Production {
private String name;
private int i = 1;
// true表示是沒有產品
private boolean flag = true;
public synchronized void setPara(String name) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + i;
System.out.println("生產者...............生產了:" + this.name);
i++;
flag = false;
this.notify();
}
public synchronized void Out() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消費者.....消費了:" + this.name);
flag = true;
this.notify();
}
}
class Producer implements Runnable {
Production p;
public Producer(Production p) {
this.p = p;
}
public void run() {
while (true) {
p.setPara("米飯");
}
}
}
class Customer implements Runnable {
Production p;
public Customer(Production p) {
this.p = p;
}
public void run() {
while (true) {
p.Out();
}
}
}
運行結果也很簡單,生產一份,消費一份,那麼,問題來了,如果我是多生產者,多消費者,還是這樣嗎?
於是,增加生產者,消費者:
Production p = new Production();
Producer pro1 = new Producer(p);
Producer pro2 = new Producer(p);
Customer cus1 = new Customer(p);
Customer cus2 = new Customer(p);
Thread t1 = new Thread(pro1);
Thread t2 = new Thread(pro2);
Thread t3 = new Thread(cus1);
Thread t4 = new Thread(cus2);
t1.start();
t2.start();
t3.start();
t4.start();
結果就不對了:
這就不是先前說好的生產一份,消費一份。
問題分析:
問題就出在wait()和notify()這兩個方法的使用上。
public synchronized void setPara(String name) {
if (!flag) {
this.wait();
}
flag = false;
this.notify();
}
public synchronized void Out() {
if (flag) {
this.wait();
}
flag = true;
this.notify();
}
}
我們知道,當只有一個生產者和消費者的話,這段代碼的意圖在於:
當沒有產品時,消費者調用wait()方法,生產者調用生產方法,當生產一件產品後,激活線程池的隨機一個線程,置flag爲反,生產者調用wait()方法等待。
有產品的時候,消費者消費,並激活線程池的隨機一個線程,生產者繼續生產,如此往復。
所以,我們看出,問題在於隨機二字,也就是說,兩個線程,我可以保證調完生產者,調消費者,但是如果有兩個及以上的生產者和消費者則不能保證:生產者和消費者是交替調用的。
那麼,怎麼發生的呢?
1.首先要知道wait()方法使其處於凍結狀態,而喚醒他,if()判斷不會得到重新判斷。
2.生產者和消費者沒有交替調用,假設有兩個生產者1和2,此時,由於上一次生產,生產者1和2已經處於wait()凍結狀態了(此句話的意思也就是if已經判斷過了!)。此時,生產者1被消費者喚醒了,它生產一個產品,置flag爲反,且隨機喚醒一個線程,那麼,剛好喚醒了生產者2,而生產者2也是不需要if(flag)的,這就導致了他不會被wait()了,而是直接執行下面的代碼了,又繼續生產了一個產品,那麼此時,就生產了兩個產品,且這種機制仍會繼續,加入下次,它又喚醒了生產者1,如此,產品會不斷生產下去。
解決辦法:使用while+notifyALL()這種組合,將if換爲while,notify換爲notifyAll:
while:解決了喚醒後不判斷的缺陷,使得不會出現生產者(消費者)多次生產(消費);
notifyAll:保證至少生產者和消費者可以交替喚醒的。(缺點:開銷好大,每次都要喚醒全部,解決:jdk1.5新特性)
思考:
1.如果只是用while(),而不使用notifyAll(),會出現什麼情況?
答:死鎖,生產者1喚醒了生產者2,生產者2也判斷,wait()了,那麼,所有線程全部處在凍結狀態了。
2.以上情況反過來呢?
答:那問題就沒有解決,問題仍會出現。
5.JKK1.5新特性Lock接口
全路徑:java.util.concurrent.locks.Lock
實現此接口的類:
ReentrantLock,
ReentrantReadWriteLock.ReadLock,
ReentrantReadWriteLock.WriteLock
使用:
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
這樣就保證了鎖一定會被解開。
用於替代wait(),notify(),notifyAll()等方法,加入了Condition接口,該接口可以通過lock.newCondition() 方法獲取。
實例:將上例生產者-消費者用新特性實現
/***
* 多線程的生產者和消費者jdk1.5的解決方法
*/
public class CustomerProducerDemo1 {
public static void main(String[] args) {
Production p = new Production();
Producer pro1 = new Producer(p);
Producer pro2 = new Producer(p);
Customer cus1 = new Customer(p);
Customer cus2 = new Customer(p);
Thread t1 = new Thread(pro1);
Thread t2 = new Thread(pro2);
Thread t3 = new Thread(cus1);
Thread t4 = new Thread(cus2);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Production {
private String name;
private int i = 1;
// true表示是沒有產品
private boolean flag = true;
// 1.5特性Lock接口
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();// 生產者的監視
Condition condition2 = lock.newCondition();// 消費者的監視
public void setPara(String name) {
// 上鎖
lock.lock();
try {
while (!flag) {
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + i;
System.out.println(Thread.currentThread().getName() + "生產者...............生產了:" + this.name);
i++;
flag = false;
condition2.signal();
} finally {
lock.unlock();
}
}
public void Out() {
lock.lock();
try {
while (flag) {
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消費者.....消費了:" + this.name);
flag = true;
condition1.signal();
} finally {
lock.unlock();
}
}
}
class Producer implements Runnable {
Production p;
public Producer(Production p) {
this.p = p;
}
public void run() {
while (true) {
p.setPara("米飯");
}
}
}
class Customer implements Runnable {
Production p;
public Customer(Production p) {
this.p = p;
}
public void run() {
while (true) {
p.Out();
}
}
}
結果沒有出現安全性問題。
優點:
以前一個鎖上只能有一組監視器方法。現在,一個Lock鎖上可以多組監視器方法對象,可以實現一組負責生產者,一組負責消費者
wait和sleep的區別:
1,wait可以指定時間也可以不指定。
sleep必須指定時間。
2,在同步中時,對cpu的執行權和鎖的處理不同。
wait:釋放執行權,釋放鎖。
sleep:釋放執行權,不釋放鎖。
6.停止線程的方式
1,stop方法。
2,run方法結束。
怎麼控制線程的任務結束呢?
任務中都會有循環結構,只要控制住循環就可以結束任務。
控制循環通常就用定義標記來完成。
但是如果線程處於了凍結狀態,無法讀取標記。如何結束呢?
可以使用interrupt()方法將線程從凍結狀態強制恢復到運行狀態中來,讓線程具備cpu的執行資格。
當時強制動作會發生了InterruptedException,記得要處理
接下來,設計一個,不可以通過run()方法循環自動結束的線程,必須使用interrupt()方法。
案例:
/**
* 線程的關閉
*/
public class StopThread {
public static void main(String[] args) {
StopThreadDemo s = new StopThreadDemo();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.start();
t2.start();
//如果不使用interrupt方法,那麼線程將永遠等待下去
t1.interrupt();
t2.interrupt();
}
}
class StopThreadDemo implements Runnable {
private boolean flag = true;
private int num = 150;
public void run() {
while (flag) {
synchronized (this) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "........" + num--);
} else {
try {
this.wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "......." + e);
flag = false;
}
}
}
}
}
}
怎麼實現的?
interrupt()方法將線程從凍結狀態強制恢復到運行狀態中來,並拋出InterruptedException,我們只需要在catch區將判斷標誌改寫爲結束,那麼通過這種方式,將會解決線程無法關閉的問題。
7.其他
線程常見的一些方法。
|--setDaemon()(守護進程,就是後臺進程,如果設置爲true的話,那麼該線程就是後臺進程。當正在運行的線程都是守護線程時,Java 虛擬機退出。 )
|--join() (臨時加入一個線程運算時可以使用join方法);
|--優先級
|--yield()( 暫停當前正在執行的線程對象,並執行其他線程。);
|--在開發時,可以使用匿名內部類來完成局部的路徑開闢。