1. 程式人生 > 實用技巧 >Java多執行緒之執行緒間的通訊

Java多執行緒之執行緒間的通訊

1.執行緒間的通訊

  • 執行緒間通訊

    • 生產者+消費者
    • 通知等待喚醒機制
  • 多執行緒程式設計模板

    • 判斷 幹活 通知
    • 判斷需使用while,以防止中斷和虛假喚醒(見java.lang.Object的API)

    A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one

       synchronized (obj) {
          	while (<condition does not hold>)
          		obj.wait(timeout);
          	... // Perform action appropriate to condition
       }
    

    執行緒也可以在沒有通知、中斷或超時的情況下被喚醒,這就是所謂的假喚醒。雖然這種情況在實踐中很少發生,但應用程式必須通過測試導致執行緒被喚醒的條件來防止這種情況發生,如果條件不滿足,則繼續等待。換句話說,等待應該總是出現在迴圈中,就像這個迴圈一樣

1.1 synchronized版

  • synchronized實現

    • 先2個執行緒操作資源類,資源中的操作判斷使用if,如執行緒A和執行緒B,可以正常執行1 0 1 0 1 0...
    • 增加2個執行緒C和D,模擬虛假喚醒,判斷依舊是if,執行的結果數字不是全部為1、0
      • 原因:在java多執行緒判斷時,不能用if,程式出事出在了判斷上面,突然有一新增的執行緒進到if了,突然中斷了交出控制權,沒有進行驗證,而是直接走下去了,加了兩次,甚至多次
    • 在4執行緒中,將資源類的if判斷改為while判斷,while是隻要喚醒就要拉回來再判斷一次
    package juc.demo;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @Description:
     *  現在兩個執行緒,
     *  可以操作初始值為零的一個變數,
     *  實現一個執行緒對該變數加1,一個執行緒對該變數減1,
     *  交替,來10輪。
     * @Package: juc.demo
     * @ClassName NotifyWaitDemo
     * @author: wuwb
     * @date: 2020/10/19 13:30
     */
    public class NotifyWaitDemo {
        public static void main(String[] args) {
            int turn = 1000;
            //資源類
            ShareData data = new ShareData();
            new Thread(() -> {
                for (int i = 0; i < turn; i++) {
                    try {
                        data.increment();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "A").start();
    
            new Thread(() -> {
                for (int i = 0; i < turn; i++) {
                    try {
                        data.decrement();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "B").start();
    
            new Thread(() -> {
                for (int i = 0; i < turn; i++) {
                    try {
                        data.increment();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "C").start();
    
            new Thread(() -> {
                for (int i = 0; i < turn; i++) {
                    try {
                        data.decrement();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "D").start();
        }
    }
    
    //資源類
    class ShareData{
        private int number = 0;
    
        public synchronized void increment() throws InterruptedException {
            //判斷  if換while
            while (number != 0) {
                this.wait();
            }
            //幹活
            number++;
            System.out.println(Thread.currentThread().getName() + ":" + number);
            //通知
            this.notifyAll();
        }
    
        public synchronized void decrement() throws InterruptedException {
            while (number == 0) {
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + ":" + number);
            this.notifyAll();
        }
    
    }
    
    

2.2 JUC版

  • Lock 及 Condition

    package juc.demo;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @Description:
     *  現在兩個執行緒,
     *  可以操作初始值為零的一個變數,
     *  實現一個執行緒對該變數加1,一個執行緒對該變數減1,
     *  交替,來10輪。
     * @Package: juc.demo
     * @ClassName NotifyWaitDemo
     * @author: wuwb
     * @date: 2020/10/19 13:30
     */
    public class NotifyWaitDemo {
        public static void main(String[] args) {
            int turn = 1000;
            //資源類
            ShareData data = new ShareData();
            new Thread(() -> {
                for (int i = 0; i < turn; i++) {
                    try {
                        data.increment();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "A").start();
    
            new Thread(() -> {
                for (int i = 0; i < turn; i++) {
                    try {
                        data.decrement();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "B").start();
    
            new Thread(() -> {
                for (int i = 0; i < turn; i++) {
                    try {
                        data.increment();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "C").start();
    
            new Thread(() -> {
                for (int i = 0; i < turn; i++) {
                    try {
                        data.decrement();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "D").start();
        }
    }
    
    //資源類
    class ShareData{
        private int number = 0;
        private Lock lock = new ReentrantLock();
        private Condition condition = lock.newCondition();
    
        public void increment() {
            lock.lock();
            try {
                //判斷
                while (number != 0) {
                    condition.await();//this.wait();
                }
                //幹活
                number++;
                System.out.println(Thread.currentThread().getName() + ":" + number);
                //通知
                condition.signalAll();//this.notifyAll();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void decrement() {
            lock.lock();
            try {
                while (number == 0) {
                    condition.await();
                }
                number--;
                System.out.println(Thread.currentThread().getName() + ":" + number);
                condition.signalAll();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
    }
    
    

3.3 定製化呼叫通訊

  • 使用Lock、Condition指定呼叫順序,指定喚醒哪個執行緒
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description
 * 多執行緒之間按順序呼叫,實現A->B->C
 * ......來10輪
 */
public class ThreadOrderAccess {
    public static void main(String[] args) {
        ShareResource resource = new ShareResource();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                resource.printA();
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                resource.printB();
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                resource.printC();
            }
        }, "C").start();
    }
}

class ShareResource{
    /**標誌位*/
    private int number = 1;
    private Lock lock = new ReentrantLock();
    /**3把鑰匙*/
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
   
    public void printA() {
        lock.lock();
        try {
            while (number != 1) {
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"==>AAAAAAAAAA");
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {
            while (number != 2) {
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"==>BBBBBBBBBB");
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {
            while (number != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"==>CCCCCCCCCC");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}