一、后台線程(守護線程)
學一個東西,最重要的一點就是,為什么要用它?
后台線程區別於普通線程,普通線程又可以稱為用戶線程,只完成用戶自己想要完成的任務,不提供公共服務。而有時,我們希望編寫一段程序,能夠提供公共的服務,保證所有用戶針對該線程的請求都能有響應。
仔細來看下后台線程的定義:指在程序運行的時候在后台提供一種通用服務的線程,並且這種線程並不屬於程序中不可或缺的部分。
二、實現后台線程
1.我們先定義任務及響應的線程
定義任務:Thread.yield();讓線程暫停一段時間
class DaemonSpawn implements Runnable{ public void run(){ while(true) Thread.yield(); } }
定一個線程,有一個屬性Thread[] t,用於存放子線程
class Daemon implements Runnable{ //該任務下創建很多子線程 private Thread[] t = new Thread[10]; public void run(){ //為線程池填充線程,並將所有線程啟動 for(int i = 0 ; i < t.length ; i++){ t[i] = new Thread(new DaemonSpawn()); t[i].start(); System.out.println("DaemonSpawn "+i+"started, "); } for(int i = 0 ; i < t.length ; i++){ System.out.println("t["+i+"].isDaemon()="+t[i].isDaemon()+", "); } /* * Daemon進入了無線循環,並在循環里調用yield方法把控制權交給其他進程 */ while(true) Thread.yield(); } }
講定義的線程設定為后台線程
public static void main(String[] args) throws InterruptedException{ /* * Daemon被設置為了后台線程,它的所有子線程也自然就是后台線程了 */ Thread d = new Thread(new Daemon()); d.setDaemon(true); d.start(); System.out.println("d.isDaemon()="+d.isDaemon()+","); TimeUnit.SECONDS.sleep(1); }
至此,后台線程已定義並跑起來了。輸出結果:
DaemonSpawn 0started, DaemonSpawn 1started, DaemonSpawn 2started, DaemonSpawn 3started, DaemonSpawn 4started, DaemonSpawn 5started, DaemonSpawn 6started, DaemonSpawn 7started, DaemonSpawn 8started, DaemonSpawn 9started, t[0].isDaemon()=true, t[1].isDaemon()=true, t[2].isDaemon()=true, t[3].isDaemon()=true, t[4].isDaemon()=true, t[5].isDaemon()=true, t[6].isDaemon()=true, t[7].isDaemon()=true, t[8].isDaemon()=true, t[9].isDaemon()=true, d.isDaemon()+true,
2.有一點要指出:所有的“非后台線程”結束時,程序也就終止了,同時會殺死進程中所有后台線程:main就是一個非后台線程
首先,如何證明main是非后台線程,還是用是上面那段程序
其次,如何證明非后台線程退出后,后台線程會被殺死呢?
public class TaskDaemon implements Runnable{ @Override public void run() { try{ while(true){ TimeUnit.MILLISECONDS.sleep(100); System.out.println(Thread.currentThread()+" "+this); } }catch(InterruptedException e){ System.out.println("sleep() interrupted"); } } public static void main(String[] args) throws InterruptedException{ /* * 可以通過查看該程序的結果理解后台線程 * 創建了9個線程,都聲明為后台線程,然后啟動他們,在非后台線程結束之前,后台線程會被線程調用器調用 * main就是一個非后台線程,for循環結束之后輸出了"All daemons started"證明main快要結束了,但是你讓它睡眠了一會保證main不退出 * 這樣后台線程就會跑着,於是有了后面的打印結果 */ for(int i = 0 ; i < 10 ; i++){ //后台線程本質上也是一個線程,通過任務來創建該線程 Thread daemon = new Thread(new TaskDaemon()); //想將創建的線程聲明為后台線程 ,必須在啟動前將其設置為true daemon.setDaemon(true); daemon.start(); } System.out.println("All daemons started"); TimeUnit.MILLISECONDS.sleep(175); } }
3.通過isDaemon()方法來判斷一個線程是否是一個后台線程
一個后台線程創建的任何線程都將被自動設置成后台線程,例如:Daemons中所示。
4.后台線程在不執行finally字句的情況下就會終止其run()方法,例如:DaemonsDontRunFinally
class ADaemon implements Runnable{ @Override public void run() { try{ System.out.println("Starting ADaemon"); TimeUnit.SECONDS.sleep(1); }catch(InterruptedException e){ System.out.println("Exiting via InterruptedException"); }finally{ System.out.println("Thie should always run?"); } } }
public static void main(String[] args){ //當最后一個非后台線程終止時,后台線程會“突然”終止 //故一旦main退出,jvm就會立即關閉所有的后台進程,而不會有任何你希望出現的確認形式 Thread t = new Thread(new ADaemon()); //如果注釋掉下面這句話,finally將會執行 t.setDaemon(true); t.start(); }
可以看到輸出結果,finally中結果並沒有執行
三、自定義后台線程工廠
1.自定義后台線程工廠
public class TaskDaemonFactory implements ThreadFactory{ public Thread newThread(Runnable r){ Thread t = new Thread(r); t.setDaemon(true); return t; } }
2.創建線程時試用該工廠
/* * Executors.newCachedThreadPool();方法用來接受一個ThreadFactory對象,而這個對象將被用來創建新的線程 * 所以,你的Facotry重寫了ThreadFacotry方法之后,要去實現他的創建線程方法,方法里默認將線程聲明為后台線程 */ ExecutorService exec = Executors.newCachedThreadPool(new TaskDaemonFactory()); for(int i = 0 ;i < 10 ;i++){ exec.execute(new TaskDaemonFromFactory());//這個是一個自定義任務 } System.out.println("All daemons started"); TimeUnit.MILLISECONDS.sleep(500);
四、總結
后台線程(daemon)
|——定義:指在程序運行的時候在后台提供一種通用服務的線程,並且這種線程並不屬於程序中不可或缺的部分
| |——所有的“非后台線程”結束時,程序也就終止了,同時會殺死進程中所有后台線程:main就是一個非后台線程
|——聲明並試用后台線程
| |——傳統方式:通過聲明線程,操作線程來定義后台線程
| | |——Thread daemon = new Thread(new TaskDaemon());//將任務交給線程也叫聲明線程
| | |—— daemon.setDaemon(true);//將線程設置為后台線程
| | |——daemon.start();//啟動后台線程
| |——由executor調用線程工廠:通過編寫定制的ThreadFactory,可以定制由Executor創建的線程的屬性
| |——1.實現ThreadFactory接口
| |——2.重寫newThread方法
| |—— public Thread newThread(Runnable r){
| |—— Thread t = new Thread(r);
| |—— t.setDaemon(true);
| |—— return t;
| |—— }
| |——3.將定制的TaskDaemonFactory傳遞給Executor,它將用此來生成對應的線程,並操縱他們
| |—— 每個靜態的ExecutorService創建方法都被重載為接受一個ThreadFactory對象,該對象將被用來創建新的線程
| |—— ExecutorService exec = Executors.newCachedThreadPool(new TaskDaemonFactory());
| |——4.將任務傳遞給exec,它會幫你執行線程操作
| |—— exec.execute(new TaskDaemonFromFactory());
注:以上代碼均來自《Thinking in java》,總結內容均是個人理解,如有錯誤請大家批評指正,謝謝