2020119-多執行緒暫停和喚醒

feint_2009發表於2020-11-19
  1. 最近在工作中有涉及到工作流的暫停和喚醒的設計和開發任務,裡面有些坑,拿出來分享下
  2. 工作流的暫停和喚醒主要有三種方式:
  3. 第一種使用wait(), notify():
  • /**
     * 執行緒阻塞/喚醒
     * 2020/11/19
     * chenzhen
     */
    public class MyThread extends Thread {
    
        private boolean isWait =false;
        //
        private final Object lock = new Object();
        //標誌執行緒阻塞情況
        private boolean pause = false;
        //設定執行緒是否阻塞
        public void pauseThread() {
            this.pause = true;
        }
    
        //呼叫該方法實現恢復執行緒的執行
        public void resumeThread() {
            this.pause = false;
            synchronized (lock) {
                //喚醒執行緒
                lock.notify();
            }
        }
    
        //這個方法只能在run方法中實現,不然會阻塞主執行緒,導致頁面無響應
        void onPause() {
            synchronized (lock) {
                try {
                    //執行緒等待/阻塞
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public void run() {
            super.run();
            //標誌執行緒開啟
            isWait = true;
            //一直迴圈
            while (true) {
                if (pause) {
                    //執行緒阻塞/等待
                    onPause();
                }
    
                try {
                    //程式每50毫秒執行一次, 值可更改
                    Thread.sleep(50);
                    //業務邏輯
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    
        public static void main(String[] args) {
            //1,建立自己的執行緒
            MyThread myThread = new MyThread();
            //2,在合適的地方啟動執行緒
            myThread.start();
            //3,啟動後執行緒的阻塞/暫停
            myThread.pauseThread();
            //4,阻塞/暫停執行緒後喚醒/繼續
            myThread.resumeThread();
        }
    }

    4, 第二種使用 await() 和signal()

  • /**
     * 執行緒阻塞/喚醒
     * 2020/11/19
     * chenzhen
     */
    public class Test {
        public static Lock lock = new ReentrantLock();
        public static int count = 0;
    
        public static Condition conditionA = lock.newCondition();
        public static Condition conditionB = lock.newCondition();
    
        public static void main(String[] args) {
            Thread t1 = new Thread() {
                @Override
                public void run() {
                    lock.lock();
                    if (count < 5) {
                        System.out.println("執行緒1未達到業務要求,暫停中,等待執行緒2處理到達到要求後喚醒");
                        try {
                            conditionA.await();// 暫停執行緒並釋放鎖
                            System.out.println("conditionA被喚醒");
                            conditionB.await();
                            System.out.println("conditionB被喚醒");
                            System.out.println("我是執行緒1後面的程式碼");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    lock.unlock();
                }
            };
    
            Thread t2 = new Thread() {
                @Override
                public void run() {
                    lock.lock();
                    while (count < 10) {
                        count++;
                        System.out.println("執行緒2業務處理中: " + count);
                        try {
                            Thread.sleep(1000);
                            if (count == 5) {
                                conditionA.signal();
                                System.out.println("喚醒執行緒1");
                                lock.unlock();// 呼叫signal()方法後,執行緒2並不會釋放鎖,需要手動釋放執行緒2才會執行
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        lock.lock();// 不加這個會報java.lang.IllegalMonitorStateException
                        System.out.println("等待3秒後conditionB會被喚醒");
                        Thread.sleep(3000);
                        conditionB.signal();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.unlock();// 這裡釋放鎖,執行緒2執行完,執行緒1才會執行
                }
            };
    
            t1.start();
            t2.start();
        }
    }

    5, 第三種使用Jdk6中的LockSupport

  • /**
     * 執行緒阻塞/喚醒-使用LockSupport 實現指定執行緒的暫停和喚醒
     * 2020/11/19
     * chenzhen
     */
    public class TestLockSupport {
        public static void main(String[] args) throws Exception {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("start...");
                    LockSupport.park(); //一直wait
                    System.out.println("continue...");
                }
            });
            thread.start();
            Thread.sleep(10000);
            LockSupport.unpark(thread); //指定t執行緒解除wait狀態
    
        }
    }

    6, 專案中的需求是: 系統中有很多工作流在執行,可以手動實現指定工作流暫停和喚醒,我採用的是第三種,

    • park和unpark可以實現類似wait和notify的功能,但是並不和wait和notify交叉,也就是說unpark不會對wait起作用,notify也不會對park起作用。
    • park和unpark的使用不會出現死鎖的情況
    • blocker的作用是在dump執行緒的時候看到阻塞物件的資訊

通過檢視原始碼可以看到park只能暫停當前執行緒,不能暫停指定執行緒, 所以我採用重寫LockSupport方式

/**
 * 執行緒阻塞/喚醒 -自定義LockSupport
 * 2020/11/19
 * chenzhen
 */
public class LockSupportImpl{
    private LockSupportImpl() {} // Cannot be instantiated.

    /**
     * 暫停指定執行緒
     * @param blocker
     * @param t
     */
    public static void park(Object blocker,Thread t) {
        //Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

    private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }

    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

    public static void park() {
        UNSAFE.park(false, 0L);
    }


    // Hotspot implementation via intrinsics API
    private static final sun.misc.Unsafe UNSAFE;
    private static final long parkBlockerOffset;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                    (tk.getDeclaredField("parkBlocker"));
            SEED = UNSAFE.objectFieldOffset
                    (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                    (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                    (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}
/**
 * 執行緒阻塞/喚醒
 * 2020/11/19
 * chenzhen
 */
public class Main {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Thread thread = new Thread(
                () -> {
                    //業務邏輯省略
                }
        );
        Future<?> submit = executorService.submit(thread);
        LockSupportImpl.park("執行緒:"+thread.getName(),thread);
        LockSupportImpl.unpark(thread);
    }
}

這裡實現了

相關文章