JAVA 并发编程 - 错误的加锁 时间: 2018-09-14 15:21 分类: 多线程,JAVA 在并发编程中,我们经常看到`synchronized`关键字的使用,没错,它的确能够使某个代码块或者方法变成线程安全同步的,但是,使用不当就会出现问题,下面是一段看似没有问题却存在线程安全的代码: ```java public class LockOnInteger implements Runnable { private static Integer x = 0; @Override public void run() { for (int i = 0; i < 1000000; i++) { synchronized (x) { x++; } } } public static void main(String[] args) throws InterruptedException { LockOnInteger instance = new LockOnInteger(); Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(x); } } ``` 上面代码中两个线程对`x`进行累加,为了确保`x`的正确性,使用`synchronized`关键字对`x`进行了加锁,我们的期望最终输出`x`的值是`2000000`,但是运行上面的代码可以发现结果总是比`2000000`小。 如果对 Java 中`Integer`对象不熟悉的话,就很难解答这个问题。由于`x++`我们一眼也看不出它具体是如何进行自增的,于是我们`javap`反编译上面的编译类,得到如下代码: ```java public class me._0o0.day1.LockOnInteger implements java.lang.Runnable { public me._0o0.day1.LockOnInteger(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public void run(); Code: 0: iconst_0 1: istore_1 2: iload_1 3: ldc #2 // int 1000000 5: if_icmpge 53 8: aload_0 9: dup 10: astore_2 11: monitorenter 12: getstatic #3 // Field x:Ljava/lang/Integer; 15: astore_3 16: getstatic #3 // Field x:Ljava/lang/Integer; 19: invokevirtual #4 // Method java/lang/Integer.intValue:()I 22: iconst_1 23: iadd 24: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 27: dup 28: putstatic #3 // Field x:Ljava/lang/Integer; 31: astore 4 33: aload_3 34: pop 35: aload_2 36: monitorexit 37: goto 47 40: astore 5 42: aload_2 43: monitorexit 44: aload 5 46: athrow 47: iinc 1, 1 50: goto 2 53: return Exception table: from to target type 12 37 40 any 40 44 40 any public static void main(java.lang.String[]) throws java.lang.InterruptedException; Code: 0: new #6 // class me/_0o0/day1/LockOnInteger 3: dup 4: invokespecial #7 // Method "":()V 7: astore_1 8: new #8 // class java/lang/Thread 11: dup 12: aload_1 13: invokespecial #9 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V 16: astore_2 17: new #8 // class java/lang/Thread 20: dup 21: aload_1 22: invokespecial #9 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V 25: astore_3 26: aload_2 27: invokevirtual #10 // Method java/lang/Thread.start:()V 30: aload_3 31: invokevirtual #10 // Method java/lang/Thread.start:()V 34: aload_2 35: invokevirtual #11 // Method java/lang/Thread.join:()V 38: aload_3 39: invokevirtual #11 // Method java/lang/Thread.join:()V 42: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 45: getstatic #3 // Field x:Ljava/lang/Integer; 48: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 51: return static {}; Code: 0: iconst_0 1: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: putstatic #3 // Field x:Ljava/lang/Integer; 7: return } ``` 观察代码`run`方法中23、24行,可以看到`i++`就是在这里进行的,这里调用了`Integer.valueOf`方法,查看`Integer`类源码中的实现: ```java /** * Returns an {@code Integer} instance representing the specified * {@code int} value. If a new {@code Integer} instance is not * required, this method should generally be used in preference to * the constructor {@link #Integer(int)}, as this method is likely * to yield significantly better space and time performance by * caching frequently requested values. * * This method will always cache values in the range -128 to 127, * inclusive, and may cache other values outside of this range. * * @param i an {@code int} value. * @return an {@code Integer} instance representing {@code i}. * @since 1.5 */ public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } ``` 这个方法的返回结果有两种情况: * i 在缓存范围内(-128~127,实际上查看源码可以看到最小值low是-128固定的,但是最大值不一定就是127,并且-128~127是必定缓存的),直接从缓存返回(虽然是直接从缓存返回,但只要i的值变了,从缓存中返回的对象相对于之前的也是改变了) * 否则 new 新建一个对象返回 看到这里应该可以知道之前代码的问题了吧,由于`x++`的操作返回的总是一个新的`Integer`对象,所以造成了`synchronized`加锁失败。 感谢[刘大神][1]指点。 [1]: https://znotes.in 标签: 无