前两天去面试,被问到了一个线程同步的问题,两个线程依次输出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();
}
}