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();
}
}