Java多線程:Java多線程同步與對象控制權

join()函數
Join的含義是:將某一線程加入成爲另外一個線程的流程之一,換言之就是等待另外一個線程執行完畢。java

public class JoinTest {
        public static void main(String[] args) throws InterruptedException {
              Thread otherThread = new Thread( new Runnable() {
                      public void run() {
                            try {
                                   for ( int i = 1; i <= 5; i++) {
                                         Thread. sleep(1000);
                                         System. out.println(i + ":輔助線程執行.." );
                                  }
                           } catch (InterruptedException e) {
                                  e.printStackTrace();
                           }
                     }
              });
              otherThread.start();

               try {
                     otherThread.join();
              } catch (InterruptedException e) {
                     e.printStackTrace();
              }
               for ( int i = 1; i <= 5; i++) {
                     Thread. sleep(500);
                     System. out.println(i + ": 主線程正在執行..." );
              }
       }
}

wait()函數notify()函數多線程

  • 若是對象調用了wait方法就會使持有該對象的線程把該對象的控制權交出去,而後處於等待狀態。併發

  • 若是對象調用了notify方法就會通知某個正在等待這個對象的控制權的線程能夠繼續運行。ide

wait方法阻塞本線程,等待其餘線程調用notify方法通知本身能夠繼續執行,也就是說這一對方法應該結合在一塊兒使用,只有當A線程在wait以後出讓資源是其餘線程有機會前進,另外的B線程notifyA才能使A線程恢復執行。在Java多線程任務裏常常會出現多個線程爭奪同一個資源,若是任由其爭奪可能會形成問題,因此有序的爭奪離不開阻塞和喚醒線程,能夠先對線程已經爭得的資源加鎖,這時其餘資源將沒法爭奪這個加鎖的資源,在試用完資源後對資源進行解鎖,使得其餘線程可以從新得到這個資源的爭奪權。(其實這個過程相似於進程的信號量加鎖解鎖)。闡明這個問題的最好例子莫過於生產者消費者的模擬:函數

在這個過程當中,生產產品、消費產品時所須要的容器是爭奪的資源,對這個這多資源在訪問時須要加解鎖:this

  • synchronized(container):對容器加鎖以阻塞其餘線程同時訪問,亦即便得其餘線程處於等待狀態;線程

  • container.wait():在容器滿時阻塞本線程把容器解鎖將容器的控制權交出去,本線程處於等待狀態;code

  • container.notify():在容器空時通知正在等待容器控制權的線程恢復運行,亦即解鎖容器;對象

public class ThreadTest {
    private List<Object> container = new ArrayList<Object>();

    public static void main(String[] args) {
        PCTest m = new PCTest();
        new Thread(new Consume(m.getContainer()), "消費者1").start();
        new Thread(new Product(m.getContainer()), "生產者1").start();
        new Thread(new Consume(m.getContainer()), "消費者2").start();
        new Thread(new Product(m.getContainer()), "生產者2").start();
    }

    public List<Object> getContainer() {
        return container;
    }

    public void setContainer(List<Object> container) {
        this.container = container;
    }
}

class Product implements Runnable {
    private List<Object> container = null;

    public Product(List<Object> lst) {
        this.container = lst;
    }

    public void run() {
        while (true) {
            synchronized (container) {
                if (container.size() >= 5) {
                    try {
                        container.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                container.add(new Object());
                System.out
                        .println(Thread.currentThread().getName() + "生產了一個產品");
                System.out.println("如今容器中一共有" + container.size() + "個產品");
                container.notify();

            }
        }
    }
}

class Consume implements Runnable {
    private List<Object> container = null;

    public Consume(List<Object> lst) {
        this.container = lst;
    }

    public void run() {

        while (true) {
            synchronized (container) {
                if (container.size() == 0) {
                    try {
                        container.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                container.remove(0);
                System.out
                        .println(Thread.currentThread().getName() + "消費了一個產品");
                System.out.println("如今容器中一共有" + container.size() + "個產品");
                container.notify();

            }
        }
    }
}

線程死鎖的問題
線程和線程若是在運行的過程當中保有一樣的資源,若是這些資源的佔用在某一時刻沒法良好分配,那麼就有可能出現死鎖:隊列

class MyRunnable implements Runnable{
    Object a;
    Object b;
    public MyRunnable(Object a, Object b) {
        this.a = a;
        this.b = b;
    }
    @Override
    public void run() {
        while(true){
            synchronized (a) {
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName()+" is running");
                }
            }
        }
    }
}
class ThreadTest{
    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();
        MyRunnable myRunnable1 = new MyRunnable(a, b);
        MyRunnable myRunnable2 = new MyRunnable(b, a);
        Thread threadA = new Thread(myRunnable1,"t1");
        Thread threadB = new Thread(myRunnable2,"t2");
        threadA.start();
        threadB.start();
    }
}

在這個例子中兩個線程在運行的過程當中必須同時保有兩個對象,那麼當對象A被一個線程鎖定而被另外一個線程須要,同時對象B被一個線程鎖定而被另外一個線程須要的時候就會出現死鎖。好比t1拿到A和B後鎖定它們運行,t2由於沒有A和B處於等待狀態,t1運行後解鎖先解鎖B尚未解鎖A,t2拿到這個B後鎖定B繼而須要A,t1解鎖A後t2沒有拿到A,t1的下一次循環拿到了這個A並鎖定A,這個時候t2須要的A被t1鎖定,t1須要的B被t2鎖定,最終A和B產生死鎖。

附上Java線程的同步原理

線程同步的基本原理

java會爲每一個object對象分配一個monitor,當某個對象的同步方法(synchronized methods )或同步塊被多個線程調用時,該對象的monitor將負責處理這些訪問的併發獨佔要求。
當一個線程調用一個對象的同步方法時,JVM會檢查該對象的monitor。若是monitor沒有被佔用,那麼這個線程就獲得了monitor的佔有權,能夠繼續執行該對象的同步方法;若是monitor被其餘線程所佔用,那麼該線程將被掛起,直到monitor被釋放。
當線程退出同步方法調用時,該線程會釋放monitor,這將容許其餘等待的線程得到monitor以使對同步方法的調用執行下去。
注意:java對象的monitor機制和傳統的臨界檢查代碼區技術不同。java的一個類一個同步方法並不意味着同時只有一個線程獨佔執行(不一樣對象的同步方法能夠同時執行),但臨界檢查代碼區技術確會保證同步方法在一個時刻只被一個線程獨佔執行。
java的monitor機制的準確含義是:任什麼時候刻,對一個指定object對象的某同步方法只能由一個線程來調用。
java對象的monitor是跟隨object實例來使用的,而不是跟隨程序代碼。兩個線程能夠同時執行相同的同步方法,好比:一個類的同步方法是xMethod(),有a,b兩個對象實例,一個線程執行a.xMethod(),另外一個線程執行b.xMethod(). 互不衝突。
wait()、notify(),notifyAll()的使用
obj.wait()方法使本線程掛起,並釋放obj對象的monitor,只有其餘線程調用obj對象的notify()或notifyAll()時,才能夠被喚醒。
obj.notifyAll()方法喚醒全部阻塞在obj對象上的沉睡線程,而後被喚醒的衆多線程競爭obj對象的monitor佔有權,最終獲得的那個線程會繼續執行下去,但其餘線程繼續等待。
obj.notify()方法是隨機喚醒一個沉睡線程,過程更obj.notifyAll()方法相似。
wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,
如:

synchronized(x){
    x.notify()
    //或者wait()
}

以上內容說明了爲何調用wait(),notify(),notifyAll()的線程必需要擁有obj實例對象的monitor佔有權。
每一個對象實例都有一個等待線程隊列。這些線程都是等待對該對象的同步方法的調用許可。對一個線程來講,有兩種方法能夠進入這個等待線程隊列。一個是當其餘線程執行同步方法時,自身同時也要執行該同步方法;另外一個是調用obj.wait()方法。
當同步方法執行完畢或者執行wait()時,其餘某個線程將得到對象的訪問權。當一個線程被放入等待隊列時,必需要確保能夠經過notify()的調用來解凍該線程,以使其可以繼續執行下去。
wait()與sleep()的區別
sleep()方法是Thread類的靜態方法,不涉及到線程間同步概念,僅僅爲了讓一個線程自身得到一段沉睡時間。sleep能夠在任何地方使用。(因此sleep只跟當前線程本身有關)
wait()方法是object類的方法,解決的問題是線程間的同步,該過程包含了同步鎖的獲取和釋放,調用wait方法將會將調用者的線程掛起,直到其餘線程調用同一個對象的notify()方法纔會從新激活調用者。(因此wait適用於多個線程同步協調時才使用的)

注意:線程調用notify()以後,只有該線程徹底從 synchronized代碼裏面執行完畢後,monitor纔會被釋放,被喚醒線程才能夠真正獲得執行權。

相關文章
相關標籤/搜索