1. 程式人生 > >多線程編程學習一(Java多線程的基礎)

多線程編程學習一(Java多線程的基礎)

while except 無法 計數器 oid 共享數據 創建 ble src

一、進程和線程的概念

進程:一次程序的執行稱為一個進程,每個 進程有獨立的代碼和數據空間,進程間切換的開銷比較大,一個進程包含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多線程的基礎)