Java併發編程(一):瞭解Thread

1 引言

線程是進程中可獨立執行的最小單位,也是CPU資源分配的基本單位

線程的四個基本屬性:

屬性 描述
編號id 線程的唯一標識
名稱 線程的名字,默認“Thread-編號id”,可自定義
類別 分爲守護線程用戶線程,可以通過setDaemon(true)設置爲守護線程
優先級 表示希望哪個線程優先執行,Java中優先級取值範圍是1~10,默認5

2 Java線程對象Thread常用的方法

1 start()

表示啓動線程,讓CPU調度器調度開啓線程持行run()方法。而直接調用run()方法只是運行當前run方法,不會開啓線程。

附上源碼:

public synchronized void start() {
      
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
              
            }
        }
    }

    private native void start0();

通過源碼,不難發現start()方法實際上是調用start0()方法啓動線程的,而start0()native方法。

簡單說下native的原理及實現機制:

native修飾的表示本地方法,一個Native Method就是一個java調用非java代碼(如C代碼)的接口,Java中的Native Method並不提供實現體。

當一個類第一次被使用到時,這個類的字節碼會被加載到內存,並且只會會被載一次。在這個被加載的字節碼的入口維持着一個該類所有方法描述符的list,這些方法描述符包含這樣一些信息:方法代碼存於何處,它有哪些參數,方法的描述符(public之類)等等。

如果一個方法描述符內有native,這個描述符塊將有一個指向該方法的實現的指針。這些實現在一些DLL文件內,但是它們會被操作系統加載到Java程序的本地方法棧。當一個帶有本地方法的類被加載時,其相關的DLL並未被加載,因此指向方法實現的指針並不會被設置。只有當本地方法被調用時,這些DLL纔會被加載,這是通過調用java.system.loadLibrary()實現的。

此外,不可不說的是,本地方法意味着和平臺有關,因此使用了native的程序可移植性都不太高,並且本地方法是有開銷的,它喪失了Java的很多優勢。

2 sleep()

線程暫停阻塞等待一段時間,時間過了就繼續。該方法是不釋放鎖的。

附源碼:

public static native void sleep(long millis) throws InterruptedException

同樣的,sleep()也是本地方法,底層也是通過C代碼調用操作系統實現的

3 yield()

t.yield()表示t線程主動讓出CPU執行權進入就緒態,所有線程重新爭搶CPU(可能出現又是t線程搶到CPU執行權)。

附源碼:

public static native void yield();

4 join()

t2線程正在運行,這時在t2線程中調用 t1.join() ,這是讓線程t2等待線程t1執行結束再運行。

此方法可以讓線程按順序持行。自己線程中調用自己的join()方法是無實際意義的。

附源碼:

 public final void join() throws InterruptedException {
       join(0);
   }
   
   public final synchronized void join(long millis)
   throws InterruptedException {
       long base = System.currentTimeMillis();
       long now = 0;

       if (millis < 0) {
           throw new IllegalArgumentException("timeout value is negative");
       }

       if (millis == 0) {
           while (isAlive()) {
               wait(0);
           }
       } else {
           while (isAlive()) {
               long delay = millis - now;
               if (delay <= 0) {
                   break;
               }
               wait(delay);
               now = System.currentTimeMillis() - base;
           }
       }
   }

join(long timeout)方法底層調用的是Object類的wait(long timeout)方法。

public final native void wait(long timeout) throws InterruptedException;

Object類的wait()方法是釋放鎖的。

3 Java線程運行狀態

在這裏插入圖片描述
過程分析:

  1. new 的狀態(NEW)

  2. start() 之後線程到達就緒狀態等待CPU調度,CPU調度了 就是運行狀態。(RUNNABLE)

  3. 調用了 TImedWaiting 就回到就緒狀態,結束之後自動到運行狀態(TIMED_WAITING)

  4. 調用 Waiting 就緒然後持行相應的方法到運行(WAITING)

  5. 調用 Blocked 等待進入同步代碼快的鎖。(BLOCKED)

  6. 結束狀態Teminated(TERMINATED)

線程被掛起:正在CPU中調度的線程,CPU把該線程丟出去,持行其他線程,這就是線程被掛起。之後又回來持續該線程。(每個CPU每次只能持行一個線程)

4 Java線程調度原理

我們知道,在任意時刻,操作系統的CPU 只能執行一條機器指令,並且每個線程只有獲取到 CPU 的使用權後,纔可以執行指令(也就是說在任意時刻,只有一個線程佔用 CPU,處於運行的狀態)。
多線程併發運行實際上是指多個線程輪流獲取 CPU 使用權,分別執行各自的任務。
線程的調度由 JVM 負責。
線程調度模型分爲兩類:時間片輪轉調度模型和搶佔式調度模型。JVM採用的是搶佔式調度模型。

  • 時間片輪轉調度模型

時間片輪轉調度模型是讓所有線程輪流獲取 CPU 使用權,並且爲每個線程分配相同的佔用 CPU 的時間片,時間片用完了則線程自動放棄CPU。

  • 搶佔式調度模型

JVM 採用的是搶佔式調度模型,根據優先級來選擇佔用CPU,優先級越高線程被持行的機率就越大,如果線程的優先級都一樣,那就隨機選擇一個線程,並讓該線程佔用 CPU。也就是如果我們同時啓動多個線程,並不能保證它們能輪流獲取到均等的時間片。
如果我們的程序想幹預線程的調度過程,最簡單的辦法就是給每個線程設定一個優先級。

5 線程創建的四種方式

5.1 繼承Thread類創建線程

/**
 * @author Carson
 * @date 2020/4/3 10:44
 */
public class MyThread {
    public static void main(String[] args) {
        new ThreadGenerator().start();
    }
}

class ThreadGenerator extends Thread {
    @Override
    public void run() {
        System.out.println("Starting thread>>>>>>>>>>>>>>>>>>>>>>");
    }
}

該方式的優缺點在於:

  • 優點:編寫簡單
  • 缺點:線程類已經繼承了Thread,不能再繼承別的類。

5.2 實現Runnable接口創建線程

/**
 * @author Carson
 * @date 2020/4/3 10:44
 */
public class MyThread {
    public static void main(String[] args) {
        new Thread(new ThreadGenerator()).start();
    }
}

class ThreadGenerator implements Runnable {
    @Override
    public void run() {
        System.out.println("Starting thread>>>>>>>>>>>>>>>>>>>>>>");
    }
}

當然,在使用Java8及更高版本時,可以直接使用Lambda表達式創建:

/**
 * @author Carson
 * @date 2020/4/3 11:22
 */
public class MyThread {
    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            System.out.println("Starting thread>>>>>>>>>>>>>>>>>>>>>>");
        }).start();
    }
}

5.3 使用Callable和Future創建線程

/**
 * @author Carson
 * @date 2020/4/3 10:44
 */
public class MyThread {
    public static void main(String[] args) throws Exception {
        ThreadGenerator threadGenerator = new ThreadGenerator();
        FutureTask futureTask = new FutureTask(threadGenerator);
        new Thread(futureTask).start();
    }
}

class ThreadGenerator implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println("Starting thread>>>>>>>>>>>>>>>>>>>>>>");
        return true;
    }
}

同樣的,也可以直接使用Lambda表達式創建:

/**
 * @author Carson
 * @date 2020/4/3 11:22
 */
public class MyThread {
    public static void main(String[] args) {
        FutureTask<Boolean> futureTask = new FutureTask<>(() -> {
            System.out.println("Starting thread>>>>>>>>>>>>>>>>>>>>>>");
            return true;
        });
        new Thread(futureTask).start();
    }
}

實現Callable接口和實現Runnable接口的不同點在於:

  • call()方法可以有返回值
  • call()方法可以聲明並拋出異常

5.4 使用線程池,例如用Executor框架

/**
 * @author Carson
 * @date 2020/4/3 11:22
 */
public class MyThread {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(6);
        /* execute方法不關心返回值。 */
        executorService.execute(new ThreadGenerator());
        /* submit方法有返回值-->Future */
        executorService.submit(new ThreadGenerator());
    }
}

class ThreadGenerator implements Runnable {
    @Override
    public void run() {
        System.out.println("Starting thread>>>>>>>>>>>>>>>>>>>>>>");
    }
}

在開發中推薦使用線程池的方式,用線程池管理線程既高效風險也較低。

點點關注,不會迷路

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