Java synchronized 中使用Integer对象遇到

2018-01-04  本文已影响0人  jeffiano

阿里巴巴口碑Android开发内推
今天心血来潮做了一个java多线程的机试题目,题目本身很简单,三个线程循环打印ABC,但代码实现的过程中却暴露了之前没有充分认识到的问题。一句话总结就是:Integer的自动装箱和拆箱机制影响了Integer作为互斥量的使用。
实现代码如下:

public class ThreadABC {
    public static Integer sMutex = new Integer(0);
    public static class PrintThread extends Thread {
        String tag;
        final int index;

        public PrintThread(String tag, int index) {
            super(tag);
            this.tag = tag;
            this.index = index;
        }

        @Override
        public void run() {
            int i = 10;
            while (i-- > 0) {
                print();
            }
        }

        public void print() {
            synchronized (sMutex) {
                boolean holdLock1 = Thread.currentThread().holdsLock(sMutex);
                System.out.println(tag + ": enter synchronized holdLock:" + holdLock1);
                while (sMutex % 3 != index) {
                    try {
                        boolean holdLock2 = Thread.currentThread().holdsLock(sMutex);
                        System.out.println(tag + ": wait holdLock:" + holdLock2);
                        sMutex.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(tag);
                System.out.println(tag + ": sMutex before increment:" + System.identityHashCode(sMutex));
                sMutex++;
                System.out.println(tag + ": sMutex after increment:" + System.identityHashCode(sMutex));
                boolean holdLock = Thread.currentThread().holdsLock(sMutex);
                System.out.println(tag + ": notify holdLock:" + holdLock);
                sMutex.notify();
            }
        }
    }

    public static void main(String[] args) {
        PrintThread threadA = new PrintThread("A", 0);
        PrintThread threadB = new PrintThread("B", 1);
        PrintThread threadC = new PrintThread("C", 2);
        threadC.start();
        threadB.start();
        threadA.start();

    }
}

程序运行结果是三个线程随机出现java.lang.IllegalMonitorStateException,出现这个异常的常规原因是线程在没有获取对象锁的情况下调用的对象的notify,wait,notifyAll方法。乍一看觉得很奇怪,sMutex的notify调用明明是在synchronized同步代码块之内的,本不应该报错。于是在调用notify之前加上检查当前线程是否获取对象锁(Thread.holdsLock(Object))的日志,打印出的结果果然是没有获得锁便调用了notify。
出现这个问题的原因是java的自动装箱/拆箱机制,通过查看编译之后的.class文件可以清楚地看出,在执行 sMutex++语句时,其实执行的是sMutex= Integer.valueOf(sMutex.intValue() + 1)。在sMutex++执行前后分别调用System.identityHashCode(sMutex),计算所得结果已经不同了,说明sMutex已经指向了一个新的Integer对象,而锁的信息是存储在对象中的,而线程没有获取新对象的锁,所以报了IllegalMonitorStateException异常。
改正的方法也很简单,把互斥量换成Object对象,使用int变量作为index,修正后代码如下:

public class ThreadABC {
    public static Object sMutex = new Object();
    public static int sIndex;

    public static class PrintThread extends Thread {
        String tag;
        final int index;

        public PrintThread(String tag, int index) {
            super(tag);
            this.tag = tag;
            this.index = index;
        }

        @Override
        public void run() {
            int i = 2;
            while (i-- > 0) {
                print();
            }
        }

        public void print() {
            synchronized (sMutex) {
                while (sIndex % 3 != index) {
                    try {
                        sMutex.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(tag);
                sIndex++;
                sMutex.notifyAll();
            }
        }
    }

    public static void main(String[] args) {
        PrintThread threadA = new PrintThread("A", 0);
        PrintThread threadB = new PrintThread("B", 1);
        PrintThread threadC = new PrintThread("C", 2);
        threadC.start();
        threadB.start();
        threadA.start();

    }
}
上一篇下一篇

猜你喜欢

热点阅读