線程初步學習-01

線程概述

什麼是線程?

幾乎 所有的操作系統都支持同時運行多個任務,一個任務通常就是一個程序,每個運行中的程序就是一個進程。當一個程序運行時,內部可能包含了多個順序執行流,每個順序執行流就是一個線程。
也可以這麼描述:一個程序運行後至少有一個進程,一個進程可以包含多個線程,但至少要包含一個線程

多線程的優勢

  1. 多線程編程簡單,效率高(能直接共享數據和資源,多進程不能)(系統城建進程時需要爲該進程重新分配系統資源,但創建線程則代價小很多,因此使用多線程來實現多任務併發比多進程效率高。
  2. 適合於開發服務程序(如Web服務、聊天服務等)。
  3. 適合於開發有多種交互接口的程序
  4. 適合於有人機交互又有計算量的程序
  5. 減輕編寫交互頻繁、涉及面多的程序的困難(如網絡監聽窗口)
  6. 程序的吞吐量會得到改善
  7. 有多個處理器的系統,可以併發運行不同的線程(否則,任何時刻只有一個線程在運行,只是宏觀上看是併發運行)

線程的創建和啓動

繼承Thread類創建線程類

  1. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務。因此把run()方法稱爲線程執行
  2. 創建Thread子類的實例,即創建了線程對象。
  3. 調用線程對象的start()方法來啓動該線程
package thread;

/**
 * @author Mike
 * @use 集成thread類創建線程
 */
public class FirstThread extends Thread {
    private int i;
    public void run(){
        for (;i<100;i++){
            //當線程集成thread類時,直接使用this即可獲取當前線程
            //thread對象的getname()返回當前線程的名字
            //因此可以直接調用getname()方法返回當前線程的名字
            System.out.println(getName() + " " +i);
        }
    }

    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            //調用thread的current()方法獲取當前線程
            System.out.println(Thread.currentThread().getName()+ " " +i);
            if (i==20){
                new FirstThread().start();
                new FirstThread().start();
            }
        }
    }
}

實現Runnable 接口創建線程類

  1. 定義Runnable接口創建線程類
  2. 創建Runnable實現類的實例,並以此實例作爲Thread的target來創建Thread對象,該Thread對象纔是真正的線程對象
  3. 調用線程對象的start()方法來啓動該線程
package thread;

/**
 * @author Mike
 * @use 實現Runnable接口創建線程類
 */
public class SecondThread implements Runnable {
    private int i;
    @Override
    public void run() {
        for (;i<100;i++){
            System.out.println(Thread.currentThread().getName()+ " "+ i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+ " "+ i);
            if (i == 20){
                SecondThread st = new SecondThread();
                //通過new Thread(target,name)方法創建新線程
                new Thread(st,"線程1").start();
                new Thread(st,"線程2").start();
            }
        }
    }
}

使用Callable 和 Future 創建線程

  1. 創建Callable接口的實現類,並且實現call()方法,該call()方法將作爲線程的執行主體,可以理解爲有返回值的run()方法
  2. 使用FutureTask對象作爲Thread對象的target創建並啓動新線程
  3. 調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值
package thread;

import javax.swing.*;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * @author Mike
 * @use 使用Callable和Future創建線程
 */
public class ThirdThread {
    public static void main(String[] args) {
        //創建Callable對象
        ThirdThread tt=new ThirdThread();
        FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
           int i = 0;
           for(;i<100;i++){
               System.out.println(Thread.currentThread().getName()+"的循環變量i的值:"+i);
           }
           //call有返回值
            return i;
        });
        for (int i = 0;i < 100; i++){
            System.out.println(Thread.currentThread().getName()+"的循環變量i的值:"+i);
            if (i == 20){
                new Thread(task,"有返回值的線程").start();
            }
        }
        try{
            System.out.println("子線程的返回值:" + task.get());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

線程的生命週期

線程的聲明週期
線程對象調用了start()方法之後,該線程處於就緒狀態
就緒狀態–>阻塞狀態

  1. 線程調用sleep()方法主動放棄所佔有的處理器資源
  2. 線程調用了一個阻塞式IO方法,在該方法返回前,該線程被阻塞
  3. 線程wait()在等待notify()時會處於阻塞狀態
  4. 程序調用了線程的suspend方法將線程掛起。但這個方法容易導致死鎖,所以應該儘量避免使用該方法。

阻塞狀態–>就緒狀態

  1. 調用sleep()方法的線程經過了指定時間
  2. 線程調用的阻塞式IO方法已經返回
  3. 線程成功的獲得了試圖取得的同步監視器
  4. 被notify()喚醒
  5. 處於掛起狀態的線程被調用了resume()恢復方法

運行狀態–>就緒狀態
yield()方法

線程從阻塞狀態只能進入就緒狀態,無法直接進入運行狀態。就緒和運行之間的狀態轉換通常不受程序控制,而是由系統線程調度所決定。
線程死亡

  1. run()方法執行完畢,線程正常結束
  2. 線程拋出一個未捕獲的Exception 或 Error
  3. 直接調用該線程的stop()方法來結束該線程–該方法容易導致死鎖,通常不推薦使用。
  • 不要對處於死亡狀態的線程調用start()方法,程序只能對新建的線程調用start()方法,對新建狀態的線程兩次調用start()方法也是錯誤的。都會引發IllegalThreadStateException異常

控制線程

wait(),notify()

兩個方法配套使用,wait()使得線程進入阻塞狀態

  1. 當wait()時,必須經過對應的notify(),才能重新進入就緒
  2. wait(long time),括號中的參數是毫秒值,超出時間或者notigy()調用都能進入就緒狀態
    還有一些涉及到同步的內容在寫同步的時候再說吧~

sleep()

sleep()方法可以讓當前正在執行的線程暫停一段時間,並進入阻塞狀態
1.sleep(long time)讓當前正在執行的線程暫停time毫秒,並進入阻塞狀態
2.還有一個sleep(long millis,int nanos)不常用,後面的nanos是微秒

package thread;

import java.util.Date;

/**
 * @author Mike
 * @use 線程睡眠
 */
public class SleepTest {
    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<10;i++){
            System.out.println("當前時間: "+ new Date());
            Thread.sleep(1000);//注意此處是毫秒爲單位
        }
    }
}//停一秒輸出一個時間

join()

當在某個程序執行流中調用其他線程的join()方法時,調用線程將被阻塞,直到被join()方法加入的join線程執行完爲止。

  1. join():等待join進來的線程執行完成
  2. join(long mills):等待join進來的線程執行mills毫秒,如果她還沒執行完。就不再等了,繼續執行。
package thread;

/**
 * @author Mike
 * @use 加入線程
 */
public class JoinThread extends Thread {
    public JoinThread(String name){
        super(name);
    }
    //重寫run()方法,定義線程執行體

    @Override
    public void run() {
        for ( int i = 0;i < 100;i++){
            System.out.println(getName() + " " +i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new JoinThread("新線程").start();
        for (int i = 0 ;i< 100;i++){
            if (i == 20){
                JoinThread jt = new JoinThread(("join進來的線程"));
                jt.start();
                jt.join();
            }
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
    //從結果上來看,剛開始時新線程和main線程在併發執行,有新的線程join進來後,main處於等待狀態,
    // 新線程和join線程併發執行
    //等join線程和新線程執行完之後繼續執行main線程
    //有個問題,join線程先執行完是沒問題的,但是爲什麼新線程會在main線程執行完之前執行完
}

yield()

和sleep()方法相似,他也可以讓當前正在執行的線程暫停,但他不會阻塞該線程,只是將該線程轉入就緒狀態
關於sleep()和yield()的區別

  1. sleep()方法暫停當前的線程後,會給其他線程執行機會,不會理會其他線程的優先級;但yield()方法只會給更高優先級或者相同優先級的線程執行機會
  2. sleeep()方法聲明拋出了InterruptException異常,所以調用sleep方法時要麼捕獲該異常,要麼拋出異常,而yield()方法則沒有拋出異常
  3. sleep()方法比yield()方法有更好的可移植性,通常不建議使用yield()方法來控制併發線程的執行
package thread;

/**
 * @author Mike
 * @use 線程讓步
 */
public class YieldTest extends Thread {
    public YieldTest(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0;i<50;i++){
            System.out.println(getName()+ " "+i);
            if (i == 20){
                Thread.yield();
            }
        }
    }

    public static void main(String[] args) {
        YieldTest yt1 = new YieldTest("高級");
        yt1.setPriority(Thread.MAX_PRIORITY);
        yt1.start();
        YieldTest yt2 = new YieldTest("低級");
        yt2.setPriority(Thread.MIN_PRIORITY);
        yt2.start();
    }
}

線程優先級setPriority(int newPriority)

此處注意優先級並不是絕對的概念,並不是優先級高的一定要執行完纔會執行優先級低的,也並不一定時優先級高的一定先執行,這是一種概率問題,比如優先級高的線程可能有80%的概率會調用執行,優先級低的有20%,但還是有可能會先調用優先級低的。

後臺線程

調用setDaemon(true)方法可以把指定線程設置成後臺線程,如果前臺線程都死亡,後臺線程會自動死亡

package thread;

/**
 * @author Mike
 * @use 後臺線程
 */
public class DaemonThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++){
            System.out.println(getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        DaemonThread dt = new DaemonThread();
        dt.setDaemon(true);
        dt.start();
        for (int i = 0 ;i < 10 ; i++){
            System.out.println(Thread.currentThread().getName()+" "+ i );
        }
    }
}
//此處發現

下面的鏈接是下一部分關於線程同步的
線程同步學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章