admin

JAVA 并发编程 - 错误的加锁
在并发编程中,我们经常看到synchronized关键字的使用,没错,它的确能够使某个代码块或者方法变成线程安全同...
扫描右侧二维码阅读全文
14
2018/09

JAVA 并发编程 - 错误的加锁

在并发编程中,我们经常看到synchronized关键字的使用,没错,它的确能够使某个代码块或者方法变成线程安全同步的,但是,使用不当就会出现问题,下面是一段看似没有问题却存在线程安全的代码:

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反编译上面的编译类,得到如下代码:

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."<init>":()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 "<init>":()V
       7: astore_1
       8: new           #8                  // class java/lang/Thread
      11: dup
      12: aload_1
      13: invokespecial #9                  // Method java/lang/Thread."<init>":(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."<init>":(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类源码中的实现:

/**
     * 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加锁失败。
感谢刘大神指点。

Last modification:September 14th, 2018 at 04:45 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment