1

Java线程间同步(诡异的IllegalMonitorStateException )

 2 years ago
source link: https://zxs.io/article/1368
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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();
    }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK