Java執行緒基礎

語言: CN / TW / HK

執行緒

執行緒和程序

程序是作業系統分配資源的最小單位,而執行緒是程式執行的最小單位,他們都是可以併發執行的。一個程序至少有一個執行緒,這些執行緒共享程序的資源空間。

執行緒簡介

每個執行緒都有一個優先順序,高優先順序的執行緒比低優先順序的執行緒先執行。優先順序的取值範圍是1到10的整數,預設是5。每個執行緒有可能被標記為一個守護執行緒。當一個執行緒建立另外一個新的執行緒物件,新的執行緒的優先順序等於建立他的執行緒的優先順序;如果新的執行緒物件是一個守護執行緒當且僅當建立他的執行緒是一個守護執行緒。

執行緒分類

Java執行緒分為守護執行緒(Daemon Thread)使用者執行緒(User Thread)。守護執行緒和使用者執行緒基本上是一樣的,唯一的區別是如果使用者執行緒全部退出執行了,不管有沒有守護執行緒虛擬機器都會退出。守護執行緒的作用是為其他的執行緒的執行提供服務,最典型的守護執行緒就是GC(垃圾回收期)。

建立執行緒

建立執行緒的方式

建立一個執行緒類有三種方式:

  • 繼承Thread類
  • 實現Runnable介面
  • 實現Callable介面

Thread

Thread簡介

Thread是建立執行緒最關鍵的一個類,這個詞本身也代表執行緒,Thread類實現了Runnable介面。

程式碼示例

public class ThreadDemo extends Thread {
	public void run() {
		for (int i = 0; i < 60; i++) {
			System.out.println(getName() + ":" + i);
		}
	}
}
public class Demo{
    public static void main(String[] args) {
        ThreadDemo t1 = new ThreadDemo();
		ThreadDemo t2 = new ThreadDemo();
		t1.start();
		t2.start();
    }
}

Runnable

Runnable簡介

Runnable是提供執行緒的介面,有一個抽象方法public abstract void run()。實現了這個介面的類必須實現它的run方法。

程式碼示例

public class Runnable implements Runnable{
    public void run() {
       public void run() {
           for (int i = 0; i < 60; i++) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
           }
	   }
	}
}
public class Demo{
    public static void main(String[] args) {
        RunnableDemo run = new RunnableDemo();
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        t1.start();
        t2.start();
    }
}

Callable和Future

Callable和Future簡介

Thread和Runnable建立執行緒不能獲取執行緒的返回值。從Java1.5開始,就提供了Callable和Future,通過他們可以在任務執行完畢之後得到任務執行結果。

  • Callable介面:可以返回一個結果或者丟擲一個異常的一個任務,實現者定義一個沒有引數的call方法。區別於Thread和Runnable的run方法,Calllable任務執行的方法是call。
  • Future介面:Future介面代表了非同步計算的結果,提供了一些方法用於檢查計算結果是否完成,獲取計算結果等。FutureTask類提供了Future介面的實現,並且實現了Runnable介面。

程式碼案例

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        return new Integer(sum);
    }
}
public class Demo{
    public static void main(String[] args) {
        MyCallable callable = new MyCallable();
        FutureTask<Integer> result = new FutureTask<Integer>(callable);
        new Thread(result).start();
        try {
            Integer value = result.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

執行緒生命週期

執行緒狀態

在Thread類中有一個內部列舉類State代表了執行緒的狀態,一個執行緒從建立到銷燬就是一個完整的生命週期。

public enum State {
    /**
     * 執行緒被建立,還沒有開始執行
     */
    NEW,
    /**
     * 執行緒執行狀態,執行狀態的執行緒是正在被Java虛擬機器執行,但是也可能正在等待作業系統的其他資源例如處理器
     */
    RUNNABLE,
    /**
     * 執行緒阻塞狀態,等待監視器鎖。處於阻塞狀態執行緒是在等待監視器鎖為了:進入同步程式碼塊/方法或者被呼叫後重	  		 * 新進入同步程式碼/方法
     */
    BLOCKED,
    /**
     * 執行緒等待狀態,一個執行緒處於等待狀態由於呼叫了以下這幾種方法:Object.wait;Thread.join;LockSupp
     * ort.park。處於等待的執行緒正在等待另一個執行緒執行一個特定的操作。
     */
    WAITING,
    /**
     * 執行緒超時等待狀態,一個執行緒處於超時等待狀態在一個特定的等待時間,由於呼叫了以下幾個方法Thread.slee
     * p;Object.wait(long);Thread.join(long);LockSupport.parkNanos;LockSupport.parkUntil。
     */
    TIMED_WAITING,
    /**
     * 執行緒結束狀態,執行緒已經執行完成了。
     */
    TERMINATED;
}

執行緒狀態轉換

執行緒狀態轉換圖

執行緒從建立後就在幾個狀態中切換。下面是一個執行緒狀態轉換圖,呼叫不同的方法就可以切換執行緒執行緒的狀態。

執行狀態&無限等待

呼叫Object.wait();Thread.join();LockSupport.park()方法可以讓執行緒從執行狀態進入到無限等待狀態。

  • wait方法

    是屬於Object類的,物件呼叫wait方法後會讓當前持有物件鎖的執行緒釋放當前物件鎖並進入等待佇列。物件呼叫notify從等待佇列隨機選擇一個執行緒喚醒去競爭物件鎖,物件呼叫notifyall會喚醒等待佇列中的所有執行緒去競爭物件鎖。

    public class Demo {
        public static void main(String[] args) {
            Demo demo = new Demo();
            Thread t1 = new Thread(() -> {
                synchronized (demo) {
                    System.out.println("t1 start");
                    try {
                        demo.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("t1 end");
                }
            });
            Thread t2 = new Thread(() -> {
               synchronized (demo) {
                   System.out.println("t2 start");
                   System.out.println("t2 end");
                   demo.notify();
               }
            });
            t1.start();
            t2.start();
        }
    }
    
  • join方法

    是屬於Thread類的,join方法是阻塞呼叫此方法的執行緒,當執行緒a呼叫執行緒b的b.join(long),執行緒a會阻塞直到執行緒b執行完成。

    public class Demo {
        public static void main(String[] args) throws Exception {
          	System.out.println("main start");
            Thread t1 = new Thread(() -> {
                System.out.println("t1 start");
                System.out.println("t1 end");
            });
            t1.start();
            t1.join();
            System.out.println("main end");
        }
    }
    
  • park方法

    是屬於LockSupport類的,LockSupport是一個執行緒阻塞工具類,所有的方法都是靜態方法,可以使用park方法來阻塞執行緒,使用unpart來喚醒執行緒。

    public class Demo {
        public static void main(String[] args) {
            System.out.println("main start");
            Thread t1 = new Thread(() -> {
                System.out.println("t1 start");
                LockSupport.park();
                System.out.println("t1 end");
            });
            t1.start();
            LockSupport.unpark(t1);
            System.out.println("main end");
        }
    }
    

執行狀態&超時等待

呼叫Object.wait(long);Thread.join(long);LockSupport.park(long)方法可以讓執行緒從執行狀態進入到等待狀態,直到到達等待時間或者主動喚醒。

  • wait(long)方法

    是屬於Object類的,當物件呼叫wait(long)後會讓當前持有物件鎖的執行緒釋放掉當前物件鎖進入等待佇列,直到到達等待時間或者物件呼叫notify或者notifyall從等待佇列中喚醒執行緒,執行緒又重新開始競爭鎖。

public class Demo {
    public static void main(String[] args) {
        Demo demo = new Demo();
        Thread t1 = new Thread(() -> {
            synchronized (demo) {
                for (int i = 0; i < 1000; i++) {
                    if (i == 500) {
                        try {
                            demo.wait(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("------t1------: " + i);
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (demo) {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("------t2------: " + i);
                }
            }
        });
        t1.start();
        t2.start();
    }
}
  • join(long)方法

    是屬於Thread類的,join(long)方法是阻塞呼叫此方法的執行緒,當執行緒a呼叫執行緒b的b.join(long),執行緒a會阻塞直到到達阻塞時間或者執行緒b執行完成。

public class Demo {
    public static void main(String[] args) throws Exception {
        System.out.println("main start");
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                System.out.println("----t1----: " + i);
            }
        });
        t1.start();
        t1.join(1);
        System.out.println("main end");
    }
}
  • parkUntil(long)和parkNanos(long)

    是屬於LockSupport類的,LockSupport是一個執行緒阻塞工具類,所有的方法都是靜態方法,可以使用parkUntil(long)和parkNanos(long)方法來阻塞執行緒。parkNanons是阻塞long時間,parkUntil是阻塞截止到long時間。

public class Demo {
    public static void main(String[] args) {
        System.out.println("main start");
        Thread t1 = new Thread(() -> {
            System.out.println("t1 start");
            LockSupport.parkNanos(3000000000L);
            System.out.println("t1 end");
        });
        t1.start();
        System.out.println("main end");
    }
}
public class Demo {
    public static void main(String[] args) throws Exception{
        System.out.println("main start");
        Thread t1 = new Thread(() -> {
            System.out.println("t1 start");
            String dateTimeStr = "2021-04-04 14:57:00";
            DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, df);
            LockSupport.parkUntil(dateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli());
            System.out.println("t1 end");
        });
        t1.start();
        System.out.println("main end");
    }
}

無限等待&阻塞狀態

物件呼叫wait方法後執行緒會進入無限等待狀態,當物件呼叫notify或者notifyAll時,執行緒將從無限等待狀態進入阻塞狀態。

阻塞狀態到執行狀態

執行緒處於阻塞狀態,如果獲取到鎖物件,就進入執行狀態。

分享到:
「其他文章」