多線程程序將單個任務按照功能分解成多個子任務來執行,每一個子任務稱爲一個線程,多個線程共同完成主任務的運行過程,這樣能夠縮短用戶等待時間,提升服務效率。本篇博客將簡單介紹Java開發中多線程的使用。html
目錄:java
程序(program)多線程
☃ 程序是爲完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼,靜態對象。併發
進程(process)ide
☃ 進程(process)是程序的一次執行過程,或是正在運行的一個程序。進程是一個動態的過程:有它自身的產生、存在和消亡的過程——生命週期this
↝ 如運行中的音樂播放器,運行的QQ操作系統
↝ 程序是靜態的,進程是動態的.net
↝ 進程做爲資源分配的單位,系統在運行時會爲每一個進程分配不一樣的內存區域
線程(thread)
☃ 進程可進一步細化爲線程,是一個程序內部的一條執行路徑。
↝ 若一個進程同一時間並行執行多個線程,稱之爲多線程運行
↝ 線程做爲調度和執行的單位,每一個線程擁有獨立的運行棧和程序計數器(pc),線程切換的開銷較小
↝ 一個進程中的多個線程共享相同的內存單元/內存地址空間(它們從同一堆中分配對象),能夠訪問相同的變量和對象。這就使得線程間通訊更簡便、高效。但多個線程操做共享的系統資源可能會帶來安全隱患。
☃ 一個Java應用程序java.exe,至少有三個線程:main()主線程,gc()垃圾回收線程,異常處理線程。固然若是發生異常,會影響主線程。
單核CPU和多核CPU概念
☃ 單核CPU,實際上是一種假的多線程,由於在一個時間單元內,也只能執行一個線程的任務(線程交替執行)。例如:有多車道(對應多線程)最終匯聚到一個收費站口(對應cpu),只有前面的車付完過路費(一個線程執行完畢),後面的車才能繼續經過,不然就要掛起等待。
☃ 若是是多核的話,才能更好的發揮多線程的效率。多條車道,多個收費口。
並行與併發
☃ 並行:多個CPU同時執行多個任務。好比:多我的同時作一個任務中不一樣的事。
☃ 併發:一個CPU(採用時間片)同時執行多個任務。好比:網上商城秒殺活動、多我的同時作同一件事。
我的對多線程,並行,併發,cpu核數之間的理解(若是有不對的地方歡迎評論指正):
(1)cpu核數是硬件條件,核數越大程序執行速度越快。在單核cpu上是僞多線程(單元時間內只能執行一個線程),只有在多核cpu上才能更好的體現多線程的優勢,換句話說多線程就是爲了提升進程運行效率,提升cpu利用率。
(2)並行和併發與cpu核數的關係:並行是多個cpu同時處理不一樣的任務,併發是一個cpu同一時間內執行多個任務。單核嚴格意義上不存在並行(即便有也是僞並行);併發不管是單核仍是多核都有可能發生,只要相同時間內同時有多個請求要執行某一段代碼或者指令就會發生併發。
(3)多線程與並行和併發的關係:多線程與並行和併發是不一樣的概念。多線程不等同於並行或併發,多線程是將複雜的進程分爲多個子進程運行,能夠理解爲多線程是爲了合理的利用系統資源。若是子進程之間相互獨立,而且運行時互不干擾,那麼能夠認爲它是並行的;若是子進程在執行時同時調用一個公共資源,那麼它就是併發的。
多線程優勢
☃ 提升應用程序的響應。對圖形化界面更有意義,可加強用戶體驗。
☃ 提升計算機系統CPU的利用率。
☃ 改善程序結構。將既長又複雜的進程分爲多個線程,獨立運行,利於理解和修改。
多線程不必定就比單線程快,若是線程都是在一個cpu中運行,那麼單線程要比多線程快,由於在一個cpu核中多線程來回交替運行須要花費更多的時間;若是是多線程在多核中運行一般就會比單線程快。
☃ 程序須要同時執行兩個或多個任務。
☃ 程序須要實現一些須要等待的任務時,如用戶輸入、文件讀寫操做、網絡操做、搜索等。
☃ 須要一些後臺運行的程序時。
Java語言的JVM容許程序運行多個線程,它經過java.lang.Thread
類來體現。
jdk1.5以前提供兩種Java API方式建立新執行線程
Thread類的特色
☃ 每一個線程都是經過某個特定Thread對象的run()方法來完成操做的,常常把run()方法的主體稱爲線程體
。
☃ 經過該Thread對象的start()方法來啓動這個線程,而非直接調用run()。
Thread類的構造器
☃ Thread() :建立新的Thread對象
☃ Thread(String threadname):建立線程並指定線程實例名
☃ Thread(Runnable target) :它實現了Runnable接口中的run方法。
☃ Thread(Runnable target, String name) :指定建立線程的目標對象和線程實例名
☃ 定義子類繼承Thread類。
☃ 子類中重寫Thread類中的run方法。
☃ 建立Thread子類對象(即線程對象)。
☃ 調用線程對象start方法:啓動線程,調用run方法。
//一、MyThread類繼承Thread class MyThread extends Thread{ // 二、重寫Thread中的run() @Override public void run() { for (int i = 1; i <= 100; i++) { if(i % 2 == 0){ System.out.println("偶數:" + i); } } } } public class ThreadTest { public static void main(String[] args) { //三、建立Thread類的子類對象 MyThread thread1 = new MyThread(); //四、子類對象調用start方法 thread1.start(); for (int i = 1; i <= 100; i++) { if (i % 2 != 0){ System.out.println("奇數:" + i); } } MyThread thread2 = new MyThread(); thread2.start(); } }
重點:
↝ 手動調用run()方法則只是普通方法,沒有啓動多線程模式。
↝ run()方法由JVM調用,何時調用,執行的過程控制都由操做系統的CPU調度決定
↝ 必須使用start方法才能啓動多線程
↝ 一個線程對象只能調用一次start()方法啓動,若是重複調用了,則將拋出「IllegalThreadStateException」異常。
Thread類的相關方法 |
---|
☄ void start(): 啓動線程,並執行線程對象的run()方法 |
☄ run(): 線程在被調用時執行的操做 |
☄ String getName(): 返回線程的名稱 |
☄ void setName(String name): 設置線程的名稱 |
☄ static Thread currentThread():返回當前線程。在Thread子類中就是this,一般用於主線程和Runnable實現類 |
☄ static void yield(): : 線程讓步 暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程,若隊列中沒有同優先級或更高優先級的線程,則忽略此方法 |
☄ join() :線程插隊,當在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b執行完之後a線程才結束阻塞狀態(低優先級的線程也能夠得到執行) |
☄ static void sleep(long millis) :(指定時間:毫秒) 睡眠,延時執行 令當前活動線程在指定時間段內放棄對CPU控制,使其餘線程有機會被執行,時間到後從新排隊。 須要拋出InterruptedException異常 |
☄ stop():強制結束線程的生命週期(過期,不推薦使用) |
☄ boolean isAlive():判斷線程是否還在生命週期內 |
調度策略
➢ 時間片
➢ 搶佔式:高優先級的線程搶佔CPU
Java的調度方法
➢ 同優先級線程組成先進先出隊列(先到先服務),使用時間片策略
➢ 對高優先級,使用優先調度的搶佔式策略
➢ MAX_PRIORITY :10
➢ NORM_PRIORITY :5
➢ MIN _PRIORITY :1
線程優先級相關方法
➢ getPriority():返回線程優先級
➢ setPriority(int newPriority):改變線程的優先級
重點說明:
➢ 建立子線程時繼承父線程的優先級
➢ 低優先級只是得到調度的機率低,並不是必定是在高優先級線程以後才被調用,除改變線程的優先級外還可對低優先級線程使用join()方法使其優先執行
代碼示例:
class TestThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++){ if(i % 2 == 0){ try { sleep(500); //睡眠,延時執行 } catch (InterruptedException e) { e.printStackTrace(); } //currentThread()獲取當前線程,getName()獲取線程名,getPriority()獲取線程優先級 System.out.println(Thread.currentThread().getName()+":" + getPriority() + ":" + i); //System.out.println(this.currentThread().getName()+":" + i); this等同於Thread } if(i % 10 == 0){ yield(); //線程讓步 } } } } class TestThread2 extends Thread{ public TestThread2(String name){ super(name); } @Override public void run() { for (int i = 0; i < 100; i++){ if(i % 2 != 0){ System.out.println(Thread.currentThread().getName()+":" + i); } if(i == 80){ this.stop(); //強制結束當前線程,已過期 } } } } public class ThreadMethodTest{ public static void main(String[] args) { TestThread1 thread1 = new TestThread1(); thread1.setName("線程1"); //設置線程名 System.out.println(Thread.currentThread()); //返回當前線程 thread1.setPriority(Thread.MAX_PRIORITY); //thread1.setPriority(7) thread1.start(); TestThread2 thread2 = new TestThread2("線程2"); //構造器方式設置線程名 thread2.start(); Thread.currentThread().setName("主線程"); for(int i = 0; i < 100; i++){ System.out.println(Thread.currentThread().getName() + i); if (i % 10 ==0){ try{ thread2.join(); //線程插隊 }catch (InterruptedException e){ e.printStackTrace(); } } } System.out.println(thread1.isAlive()); //判斷線程是否還在生命週期內 System.out.println(thread2.isAlive()); } }
☃ 定義子類,實現Runnable接口。
☃ 子類中重寫Runnable接口中的run方法。
☃ 經過Thread類含參構造器建立線程對象。
☃ 將Runnable接口的子類對象做爲實際參數傳遞給Thread類的構造器中。
☃ 調用Thread類的start方法:開啓線程,調用Runnable子類接口的run方法。
//一、建立Runnable接口實現類 class RunnableThread implements Runnable{ //實現Runnable中的抽象方法run() @Override public void run() { for (int i = 0; i < 100; i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName()+":" + Thread.currentThread().getPriority() + ":" + i); } } } } public class RunnableTest { public static void main(String[] args) { //三、建立實現類的對象 RunnableThread r1 = new RunnableThread(); //四、將Runnable接口的子類對象做爲實際參數傳遞給Thread類的構造器中 Thread t1 = new Thread(r1); //五、調用Thread類的start方法:開啓線程,調用Runnable子類接口的run方法 t1.setName("線程一"); t1.start(); Thread t2 = new Thread(r1); t2.setName("線程二"); t2.start(); } }
區別
➢ 繼承Thread:線程執行代碼存放Thread子類run方法中。
➢ 實現Runnable:線程代碼存在接口的子類的run方法中。
聯繫
➢ Thread也是實現了Runnable中的run()方法。
Runnable接口線程的優勢
➢ 避免了單繼承的侷限性
➢ 多個線程能夠共享同一個接口實現類的對象,很是適合多個線程來處理同一份資源
練習:
三個窗口售賣100張票
//繼承Thread方式 class SellTicket extends Thread{ private static int ticketNum = 100; public SellTicket(String windownName){ super.setName(windownName); } @Override public void run() { while(true){ if(ticketNum > 0){ System.out.println(getName() + "窗口賣票,票號爲:" + ticketNum--); }else{ System.out.println("票已賣光"); break; } } } } public class Thread_SellTicket { public static void main(String[] args) { SellTicket t1 = new SellTicket("窗口1"); SellTicket t2 = new SellTicket("窗口2"); SellTicket t3 = new SellTicket("窗口3"); t1.start(); t2.start(); t3.start(); } } *********************************************** //實現Runnable接口方式 class R_SellTicket implements Runnable{ private int ticketNum = 100; @Override public void run() { while(true){ if(ticketNum > 0){ System.out.println(Thread.currentThread().getName() + "窗口賣票,票號爲:" + ticketNum--); }else{ System.out.println("票已賣光"); break; } } } } public class Runnable_SellTicket { public static void main(String[] args) { R_SellTicket r = new R_SellTicket(); Thread t1 = new Thread(r, "窗口1"); Thread t2 = new Thread(r, "窗口2"); Thread t3 = new Thread(r, "窗口3"); t1.start(); t2.start(); t3.start(); } } //存在線程安全問題,待解決
本博客與CSDN博客༺ཌ༈君☠纖༈ད༻同步發佈