1. 程式人生 > >Java執行緒間同步(詭異的IllegalMonitorStateException )

Java執行緒間同步(詭異的IllegalMonitorStateException )

  前兩天去面試,被問到了一個執行緒同步的問題,兩個執行緒依次輸出1……100,一個執行緒只輸出奇數,一個只輸出偶數。之前工作中沒寫過執行緒同步的程式碼,只知道使用object的wait()和notify()方法可以實現執行緒同步,之前也看過執行緒池實現的程式碼,用的也是wait()和notify()。 面試過程中沒寫出來,於是想回來學習下多執行緒的同步,然後就有了今天這詭異的事。  
  思路很簡單,建立兩個執行緒threadEven和threadOdd分別來輸出偶數和奇數,用一個Integer cnt來做資料同步,每個執行緒執行的時候先鎖住cnt,然後輸出cnt並把cnt+=1,然後通知另一個執行緒來執行並把本執行緒wait()掛起,於是有了下面的程式碼

public class ThreadA {
    private Integer cnt = new Integer(0);
    class ThreadEven extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (cnt) {
                    try {
                        Thread.sleep(100);
                    } catch
(InterruptedException e) { e.printStackTrace(); } System.out.println("threadEven " + cnt); cnt++; cnt.notify(); try { cnt.wait(); } catch
(InterruptedException e) { e.printStackTrace(); } if (cnt > 100) { return; } } } } } class ThreadOdd extends Thread { @Override public void run() { while (true) { synchronized (cnt) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("threadOdd " + cnt); cnt++; cnt.notify(); try { cnt.wait(); } catch (Exception e) { e.printStackTrace(); } if (cnt > 100) { return; } } } } } public static void main(String[] args) { ThreadA test = new ThreadA(); ThreadEven threadEven = test.new ThreadEven(); ThreadOdd threadOdd = test.new ThreadOdd(); threadEven.setName("threadEven"); threadOdd.setName("threadOdd"); threadEven.start(); threadOdd.start(); } }

  程式碼看起來很完美,但執行後直接給我拋java.lang.IllegalMonitorStateException,上網查下這個exception,有三種情況下會出現這種Exception。
1. 當前執行緒不含有當前物件的鎖資源的時候,呼叫obj.wait()方法。
2. 當前執行緒不含有當前物件的鎖資源的時候,呼叫obj.notify()方法。
3. 當前執行緒不含有當前物件的鎖資源的時候,呼叫obj.notifyAll()方法。

  程式碼中很明顯我先對cnt做了同步,所以當前執行緒在執行中肯定是有cnt的鎖的,那為什麼我調cnt.notify();cnt.wait();的時候還會拋Exception? 上網查了好多資料後終於找到了問題。。

Integer是個不變類,任何對它的修改都會生成一個新的物件,同樣的不變類還有String , Boolean, Double 。

  所以上面程式碼的問題就很明顯了,我用synchronized鎖住的cnt和執行cnt+=1後的cnt就不是同一個物件,而我程式碼中也沒有獲取cnt+=1後的cnt的鎖,所以在執行 cnt.notify();cnt.wait();的時候會拋Exception。所以解決方法也很簡單,把synchronized鎖住的物件換成一個不變的就行,任何不變物件都可以,這裡我用了concurrent.atomic.AtomicInteger,所以最終可正常執行的程式碼如下。

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadA {
    private AtomicInteger cnt = new AtomicInteger();

    class ThreadEven extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (cnt) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("threadEven " + cnt);
                    cnt.addAndGet(1);
                    cnt.notify();
                    try {
                        cnt.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (cnt.get()> 100) {
                        return;
                    }
                }
            }
        }
    }
    class ThreadOdd extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (cnt) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("threadOdd  " + cnt);
                    cnt.addAndGet(1);
                    cnt.notify();
                    try {
                        cnt.wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (cnt.get() > 100) {
                        return;
                    }
                }
            }
        }
    }
    public static void main(String[] args)  {
        ThreadA test = new ThreadA();
        ThreadEven threadEven = test.new ThreadEven();
        ThreadOdd threadOdd = test.new ThreadOdd();
        threadEven.start();
        threadOdd.start();
    }
}