1. 程式人生 > >java多執行緒-02-基本操作及執行緒通訊示例

java多執行緒-02-基本操作及執行緒通訊示例

宣告

該系列文章只是記錄本人回顧java多執行緒程式設計時候記錄的筆記。文中所用語言並非嚴謹的專業術語(太嚴謹的術語其實本人也不會……)。難免有理解偏差的地方,歡迎指正。
另外,大神請繞路。不喜勿噴。
畢竟好記性不如爛筆頭嘛,而且許多東西只要不是你經常用的最終都會一丟丟一丟丟地給忘記。

1 執行緒的相關概念

1.1 執行緒狀態

先看看下面這張來自百度的java執行緒狀態圖:

執行緒狀態圖

  • 新建狀態(New)

用new語句建立的執行緒處於新建狀態,只是為他分配了記憶體,並沒有開始執行。

  • 就緒狀態(Runnable)

呼叫start()方法,執行緒就進入就緒狀態。
然而也僅僅是處於可執行狀態,到底能不能立即執行還要看作業系統的排程。

  • 執行狀態(Running)

此時是真正的處於執行狀態。是由可執行狀態到執行狀態。

  • 阻塞狀態(Blocked)

執行緒由於某些原因放棄CPU的佔有權。當執行緒處於阻塞狀態時。直到下次該執行緒處於可執行狀態時才有可能再次進入執行狀態。

可能的阻塞原因:

- 呼叫了wait方法,進入了等待池。
- 等待某些資源可用。
- I/O等待、呼叫了sleep方法、其他執行緒的join方法等。
  • 死亡狀態(Dead)

執行緒執行結束。注意一個已經死亡的執行緒是沒辦法“復活”的。只能重新new一個。

1.2 優先順序

大多數資料都顯示,java中的執行緒優先順序並不會生效,往往會被作業系統直接忽略。
所以,此處對這部分內容直接略過。

1.3 daemon執行緒

daemon執行緒又稱為後臺執行緒或精靈執行緒。

可以通過setDaemon(boolean isDeamon)將一個執行緒設定為後臺執行緒。比如java的垃圾回收執行緒。

注意:

  • 當JVM中不存在非daemon執行緒的時候,JVM就會退出。
  • 將一個執行緒設定為Daemon執行緒,必須在其啟動之前進行設定。

1.4 中斷

java執行緒的中斷表示的是:某個執行緒是否被其他執行緒中斷過。相當於一個boolean型別的標識位。
此處的中斷過類似於你正在寫程式碼,突然有人找你說話,此處可以理解為被中斷了。

  • isInterrupted()
    :是否被中斷(如果執行緒已經結束了,也會返回false)
  • Thread.interrupted():復位中斷標誌

執行緒被中斷後丟擲java.lang.InterruptedException異常,但是在異常被丟擲之前,JVM會先將中斷標識位復位。也就是說,在丟擲java.lang.InterruptedException異常之後並且在下次中斷之前呼叫isInterrupted()方法會返回false。

1.5 suspend resume stop

這三個API都是deprecated的。已經被廢棄。此處提出來只是為了知識點的完整性。

  • suspend():暫停(睡眠)執行緒
  • resume():恢復執行緒
  • stop():終止執行緒

在《java併發程式設計的藝術》一書中,作者舉了個很形象的例子來說明這三個方法的作用:CD機播放音樂時暫停(suspend)、恢復(resume)和停止(stop)的操作。同時,作者也說明了這三個人性化的方法之所以過時是因為:

  • suspend方法會一致佔有著擁有的資源進入睡眠狀態
  • stop方法在終止的執行緒的時候對資源的釋放沒有保證

1.6 物件監視器

任何物件都有自己的監視器。當某個物件被同步方法或者同步塊呼叫時,執行方法的執行緒必須先獲得其監視器。
沒有獲取到監視器的執行緒將被阻塞在方法的入口處進入阻塞(blocked)狀態。

所以,任何物件都可以被當做鎖來使用。

1.7 wait()和sleep()

  • 兩者都可以讓執行緒等待
  • wait()呼叫時,必須先獲取到鎖
  • wait()是Object的方法
  • sleep()是Thread的方法

2 執行緒通訊

一般而言,執行緒通訊至少有兩種方式:訊息傳遞和共享記憶體。

在java裡一般是共享記憶體實現的執行緒通訊。當然也有第三方的訊息傳遞模型的執行緒通訊。
下文所說的執行緒通訊指的是共享記憶體模型。

2.1 基本概念

和執行緒通訊相關的方法

method DESC
notify() 通知一個在物件上等待的執行緒,使其從wait()方法返回,返回的前提是該執行緒獲取到了物件的鎖
notifyAll() 通知所有等待在該物件上的執行緒
wait() 呼叫wait方法的執行緒將釋放鎖(如果已經持有鎖的話)並進入waiting狀態。只有等待其他執行緒的通知或被中斷才會返回。
wait(long t) 和wait方法的不同是:等待t毫秒還是沒有被喚醒的話會“超時返回”
wait(long,int) 對超時時間控制更加細緻的wait(long)版本
join() 執行緒T呼叫tx.join()的意思是:T等待tx執行緒結束後才從tx.join()返回,接著執行T自己的後續程式碼

幾個注意點

  • 呼叫wait、notify、notifyAll時必須先獲得鎖
  • 呼叫wait方法後,執行緒有running狀態–>waitting狀態,並將當前執行緒至於物件的等待池中。
  • 呼叫notify或者notifyAll方法後waiting狀態的執行緒能夠返回的前提是:呼叫notifyAll或notify方法的執行緒先釋放鎖,並且該執行緒獲得了鎖。
  • notify呼叫後,會將等待池(等待佇列)中的一個執行緒移動到同步佇列中。被移動的執行緒狀態:waiting–>blocked。
  • notifyAll方法呼叫後,會將等待池中所有的執行緒移動至同步佇列中。被移動的執行緒狀態:waiting–>blocked。

join()的一個有趣的例子

* 注意:該示例來自《java併發程式設計的藝術》一書*

import java.util.concurrent.TimeUnit;

public class Join {
    public static void main(String[] args) throws Exception {
        Thread previous = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            // 每個執行緒擁有前一個執行緒的引用,需要等待前一個執行緒終止,才能從等待中返回
            Thread thread = new Thread(new Runner(previous), String.valueOf(i));
            thread.start();
            previous = thread;
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(Thread.currentThread().getName() + " terminate.");
    }

    static class Runner implements Runnable {
        private Thread previous;

        public Runner(Thread thread) {
            this.previous = thread;
        }

        public void run() {
            try {
                previous.join();
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread().getName() + " terminate.");
        }
    }
}

以上示例的結果其實就是所有執行緒依次序列執行。

2.2 執行緒同步示例

此處是本人改寫的一個生產者和消費者的案例。

生產者不斷生成,消費者不斷消費。

public class ProducerConsumer {

    public static void main(String[] args) {
        Container container = new Container(5);
        for (int i = 0; i < 10; i++) {
            new Thread(new Producer(container), "P-" + i).start();
            new Thread(new Consumer(container), "C-" + i).start();
        }
    }

    public static class Product {
        private String name;

        public Product(String name) {
            super();
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "[name=" + name + "]";
        }

    }

    public static class Container {
        private int nextIndex = 0;
        Product[] products = null;

        public Container(int size) {
            this.products = new Product[size > 0 ? size : 5];
        }

        public void push(Product product) {
            synchronized (products) {
                while (this.nextIndex >= this.products.length) {
                    try {
                        // 訪問products的執行緒先wait
                        this.products.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 叫醒在products等待池上等待的執行緒
                this.products.notifyAll();
                this.products[nextIndex++] = product;
            }
        }

        public Product pop() {
            synchronized (products) {
                while (this.nextIndex <= 0) {
                    try {
                        this.products.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 叫醒在products等待池上等待的執行緒
                this.products.notifyAll();
                return this.products[--nextIndex];
            }
        }
    }

    public static class Producer implements Runnable {

        private Container container;

        public Producer(Container container) {
            super();
            this.container = container;
        }

        @Override
        public void run() {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                Product p = new Product(Thread.currentThread().getName() + "_" + i);
                this.container.push(p);
                System.out.println("生產者[" + Thread.currentThread().getName() + "]生產>>>>>>:" + p);
                try {
                    Thread.sleep((long) (Math.random() * 1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public static class Consumer implements Runnable {

        private Container container;

        public Consumer(Container container) {
            super();
            this.container = container;
        }

        @Override
        public void run() {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                Product product = this.container.pop();
                System.out.println("消費者[" + Thread.currentThread().getName() + "]消費<<<<<<:" + product);
                try {
                    Thread.sleep((long) (Math.random() * 2000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

參考資料

  • 《java併發程式設計的藝術》