多線程編程學習一(Java多線程的基礎)
一、進程和線程的概念
進程:一次程序的執行稱為一個進程,每個 進程有獨立的代碼和數據空間,進程間切換的開銷比較大,一個進程包含1—n個線程。進程是資源分享的最小單位。
線程:同一類線程共享代碼和數據空間,每個線程有獨立的運行棧和程序計數器(PC),線程切換開銷小,線程是CPU調度的最小單位。
多進程:指操作系統能同時運行多個任務(程序)。
多線程:指同一個程序中有多個順序流在執行,線程是進程內部單一控制序列流。
二、多線程的優勢
單線程的特點就是排隊執行,也就是同步。而多線程能最大限度的利用CPU的空閑時間來處理其他的任務,系統的運行效率大大提升,使用多線程也就是在執行異步。
三、使用多線程
實現多線程編程的方式主要有兩種,一種是繼承Thread類,另一種是實現Runable接口。其實,使用繼承Thread類的方式創建新線程時,最大的局限就是不支持多繼承,因為Java語言的特點就是單根繼承,所以為了支持多繼承,完全可以實現Runnable接口,一邊實現一邊繼承。但是這兩種方式創建的線程在工作時的性質是一樣的,沒有本質的差別。
public class Thread1 extends Thread { private int count=5; @Override public void run() {for (int i=0;i<2;i++){ System.out.println("現在是線程"+currentThread().getName()+"在執行:"+count--); } } }
public class Thread2 implements Runnable { private int count=5; @Override public void run() { for(int i=0;i<2;i++){ System.out.println("現在是線程"+Thread.currentThread().getName()+"在執行:"+count--); } } }
public class Test{ public static void main(String[] args){ //集成Thread類 Thread1 thread1=new Thread1(); Thread t1=new Thread(thread1,"A"); Thread t2=new Thread(thread1,"B"); Thread t3=new Thread(thread1,"C"); t1.start(); t2.start(); t3.start(); //實現Runable接口 Thread2 thread2=new Thread2(); Thread t4=new Thread(thread2,"A2"); Thread t5=new Thread(thread2,"B2"); Thread t6=new Thread(thread2,"C2"); t4.start(); t5.start(); t6.start(); }}
演示這個結果是為了說明以下下兩點:
1、CPU對線程的調度具有不確定性,采用“搶占式”調度。
2、對於網上經常說的,實現Runnable接口的線程可以實現共享數據,而繼承Thread的線程就不能。其實不然,它們兩者的區別僅是單繼承的限制以及一些用法的不同(比如 如果你想對這個Thread對象做點別的事情(比如getName),那麽你就必須通過調用Thread.currentThread()方法得到對此線程的引用),沒有實質的差別。
多線程執行時為什麽調用的是start()方法而不是run()方法? 如果調用代碼thread.run()就不是異步執行了,而是同步,那麽此線程對象就不會交給“線程規劃器”來進行處理。而是由main主線程來調用run()方法,也就是說必須要等到run()方法中的代碼執行完成後才可以執行後面的代碼。start()用來啟動一個線程,當調用start()方法時,系統才會開啟一個線程,通過Thead類中start()方法來啟動的線程處於就緒狀態(可運行狀態),此時並沒有運行,一旦得到CPU時間片,就自動開始執行run()方法。
四、synchronized 關鍵字
多線程的鎖機制,通過在多線程要調用的方法前加入synchronized 關鍵字,使多個線程在執行方法時,要首先嘗試去拿這把鎖,如果能夠拿到這把鎖,那麽這個線程就可以執行synchronize裏面的代碼。如果不能拿到這把鎖,那麽這個線程就會不斷地嘗試拿這把鎖,直到拿到這把鎖。synchronized 可以在任意對象及方法上加鎖,而加鎖的這段代碼稱為“互斥區” 或 “臨界區”。
使用synchronized關鍵字主要是為了保證當前線程在執行過程中,不被其他線程搶占並修改了共享的資源,從而導致線程不安全的情況出現。
五、常用線程方法
1、Thread.currentThread()方法:返回代碼段正在被哪個線程調用的信息。最常見的就是Thread.currentThread().getName()。
2、isAlive()方法:判斷當前的線程是否處於活動狀態。什麽是活動狀態呢?活動狀態就是線程已經啟動且尚未終止。線程正在運行或準備開始運行的狀態,就認為線程是“存活”的。
3、Thread.sleep()方法:在指定的毫秒數內讓"正在執行的線程"休眠(暫停執行)。這個“正在執行的線程”是指this.currentThread()返回的線程。
4、getId()方法:取得該線程的唯一標識。
5、Thread.interrupt()方法:用於中斷線程,這裏需要註意Thread.interrupt 的作用其實也不是中斷線程,而是「通知線程應該中斷了」,具體到底中斷還是繼續運行,應該由被通知的線程自己處理。具體來說,當對一個線程,調用 interrupt() 時,
① 如果線程處於被阻塞狀態(例如處於sleep, wait, join 等狀態),那麽線程將立即退出被阻塞狀態,並拋出一個InterruptedException異常,並且清除停止狀態值,使之變為false。
② 如果線程處於正常活動狀態,那麽會將該線程的中斷標誌設置為 true,僅此而已。被設置中斷標誌的線程將繼續正常運行,不受影響。
Thread thread = new Thread(() -> { while (!Thread.interrupted()) {//通過這樣來檢查這個中斷標誌位是否設置為true,是否進行程序邏輯,請不要使用廢棄的Thread.stop, Thread.suspend, Thread.resume // do more work. } }); thread.start(); // 一段時間以後 thread.interrupt();
值得一提的是,判斷線程是否中斷有兩個辦法:
- interrupted():測試當前線程是否已經是中斷狀態,執行後具有將狀態標誌清除的false的功能。(這裏需要特別註意的是即使是MyThread.interrupted(),測試的仍然是當前線程的狀態)。
- isInterrupted():測試線程Thread對象是否已經是中斷狀態,但不清除狀態標誌。
通過拋出異常來中斷線程:
public class MyThread extends Thread { @Override public void run(){ try { for (int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("已經是停止狀態了!我要退出了"); throw new InterruptedException(); } System.out.println("i=" + (i + 1)); } } catch (InterruptedException e) { System.out.println("進入MyThread.java類run方法中的catch了!"); e.printStackTrace(); } } }
另外,還可以通過retuen的方法來中斷線程。不過還是建議"拋異常"的方法來實現線程的停止,因為在catch塊中還可以將異常向上拋,使線程停止的事件得以傳播。
6、Thread.yield()方法:放棄當前的CPU資源,將它讓給其他的任務去占用CPU的執行時間。但放棄的時間不確定,有可能剛剛放棄,馬上又獲得CPU時間片。
7、setPriority()方法:為了程序的可移植性,建議植使用 MAX_PRIORITY , NORM_PRIORITY , MIN_PRIORITY 三個級別。設置優先級並不意味著優先級低的就得不到調用,只是CPU更傾向於讓高的優先級先執行,但是CPU具體調用那個線程是無法確定的,設置優先級只能保證說這個線程被調用的頻率比較高。
8、setDaemon(true):守護線程。守護線程是一個特殊的線程,它的特性有“陪伴”的含義,當進程中不存在非守護線程了,則守護線程自動銷毀。典型的守護線程就是垃圾回收線程,當進程中沒有非守護線程了,則垃圾回收線程也就沒有存在的必要了。
9、join()方法:等待該線程終止。join() 方法主要是讓調用該方法的thread完成run方法裏面的東西後, 再執行join()方法後面的代碼,對join()方法的調用可以被中斷,做法是調用線程上的的interrupt()方法。
六、其他
1、stop()方法作廢:如果強制讓線程停止則有可能使一些清理性的工作得不到完成。另外一種情況就是對鎖定的對象進行了“解鎖”,導致數據得不到同步的處理,出現數據不一致的問題。
2、suspend()方法暫停線程。
3、resume()方法恢復線程的執行。
4、在使用suspend()和resume()時,如果使用不當,極易造成公共的同步對象的獨占,使得其他線程無法訪問公共同步對象。
多線程編程學習一(Java多線程的基礎)