JAVA多線程--線程阻塞與喚醒


方式1:早期JAVA采用suspend()、resume()對線程進行阻塞與喚醒,但這種方式產生死鎖的風險很大,因為線程被掛起以后不會釋放鎖,可能與其他線程、主線程產生死鎖,如:

public class ThreadSuspendTest {
    public static void main(String[] args) {
        Thread mt = new MyThread();
        mt.start();
        try {
            Thread.currentThread().sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        mt.suspend();
        System.out.println("suspend complete?");
        mt.resume();
    }
    static class MyThread extends Thread {
        public void run() {
            while (true) {
                System.out.println("running....");
            }
        }
    }
}
View Code

方式2:wait、notify形式通過一個object作為信號,object的wait()方法是鎖門的動作,notify()、notifyAll()是開門的動作,某一線程一旦關上門后其他線程都將阻塞,直到別的線程打開門。notify()准許阻塞的一個線程通過,notifyAll()允許所有線程通過。如下例子:主線程分別啟動兩個線程,隨后通知子線程暫停等待,再逐個喚醒后線程拋異常退出。

public class ObjectWaitTest {
    public static Object waitObject = new Object();
    
    public static void notifyAllThread() {
        System.out.println("notifyAllThread");
        synchronized (waitObject) {
            waitObject.notifyAll();
        }
    }
    public static void notifyThread() {
        System.out.println("notifyThread");
        synchronized (waitObject) {
            waitObject.notify();
        }
    }
    public static void main(String[] args) {
        MyThread tm1 = new MyThread(waitObject);
        tm1.setName("tm1");
        tm1.start();
        MyThread tm2 = new MyThread(waitObject);
        tm2.setName("tm2");
        tm2.start();
        try {
            Thread.currentThread().sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tm1.suspendThread();
        tm2.suspendThread();
        try {
            Thread.currentThread().sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        notifyThread();
        try {
            Thread.currentThread().sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        notifyThread();    
    }
    
    static class MyThread extends Thread {
        public Object waitObject = null;
        private boolean isStop = false;

        public MyThread(Object waitObject) {
            this.waitObject = waitObject;
        }

        public void run() {
            while (true) {
                synchronized (waitObject) {
                    if (isStop) {
                        System.out.println(Thread.currentThread().getId() + " is stop");
                        try {
                            waitObject.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getId() + " is resume");
                        System.out.println(Thread.currentThread().getId() + " will  exit");
                        throw new RuntimeException(Thread.currentThread().getId() +" exit");
                    }
                }
            }
        }

        public void suspendThread() {
            this.isStop = true;
        }
    }

}
View Code

 wait、notify使用要點:

1、對象操作都需要加同步synchronized;

2、線程需要阻塞的地方調用對象的wait方法;

存在的不足:面向對象的阻塞是阻塞當前線程,而喚醒的是隨機的一個線程或者所有線程,偏重線程間的通信;同時某一線程在被另一線程notify之前必須要保證此線程已經執行到wait等待點,錯過notify則可能永遠都在等待。

方式3:LockSupport提供的park和unpark方法,提供避免死鎖和競態條件,很好地代替suspend和resume組合。

 

public class ThreadParkTest {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.setName("mt");
        mt.start();
        try {
            Thread.currentThread().sleep(10);
            mt.park();
            Thread.currentThread().sleep(30000);
            mt.unPark();
            Thread.currentThread().sleep(30000);
            mt.park();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    static class MyThread extends Thread {

        private boolean isPark = false;
        public void run() {
            System.out.println(" Enter Thread running.....");
            while (true) {
                if (isPark) {
                    System.out.println("Thread is Park.....");
                    LockSupport.park();
                }
            }
        }
        public void park() {
            isPark = true;
        }
        public void unPark() {
            isPark = false;
            LockSupport.unpark(this);
            System.out.println("Thread is unpark.....");
        }
    }
}
View Code

 

park與unpark方法控制的顆粒度更加細小,能准確決定線程在某個點停止,進而避免死鎖的產生。

park與unpark引入了許可機制,許可邏輯為:

①park將許可在等於0的時候阻塞,等於1的時候返回並將許可減為0;

②unpark嘗試喚醒線程,許可加1。根據這兩個邏輯,對於同一條線程,park與unpark先后操作的順序似乎並不影響程序正確地執行,假如先執行unpark操作,許可則為1,之后再執行park操作,此時因為許可等於1直接返回往下執行,並不執行阻塞操作。

park與unpark組合真正解耦了線程之間的同步,不再需要另外的對象變量存儲狀態,並且也不需要考慮同步鎖,wait與notify要保證必須有鎖才能執行,而且執行notify操作釋放鎖后還要將當前線程扔進該對象鎖的等待隊列,LockSupport則完全不用考慮對象、鎖、等待隊列等問題。

總結:suspend()、resume()已經被deprecated,不建議使用。wait、notify需要對對象加同步,性能有折扣。LockSupport則完全不用考慮對象、鎖、等待隊列。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM