Synchronized对实例对象的成员变量同步时是否还会进行代码的重排序

代码如下图所示,代码逻辑主要是启动多个线程,对同一个实例的成员变量进行同步和操作:

public class SynTest {

    private Integer obj = 1;
    /**
     * @param args
     */
    public static void main(String[] args) {
        final SynTest synTest = new SynTest();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synTest.add();
                }
            }).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
    }
    public void add() {
        synchronized (obj) {
            System.out.println(Thread.currentThread());
            obj++;
            System.out.println(Thread.currentThread() + "===" + obj);
            obj++;
            System.out.println(Thread.currentThread() + "-" + obj);
            obj++;
            System.out.println(Thread.currentThread());
        }
    }
}

运行结果如下:

我的问题点主要是:  

1. 为什么单个线程的操作不是连续的?我理解只有一个线程获取到锁,然后进行操作并输出结果,那么输出结果应该是一个线程输出完,另一个线程才会继续输出

2.JVM会对同步的代码块进行同步消除优化?实际的代码应该是和下图所示?

1. 为什么单线程操作不是连续的,这是因为虽然你使用了  synchronized (obj) {}  来尝试获取排它锁,但是其他线程把obj修改了,比如从1修改到2 ,又因为Integer使用了享元模式,1 和 2 不是同一个对象,所以你的每个线程可能锁的不是同一个对象,所以也就无法按照你的需求输出,将obj修改为final常量后,各个线程锁定的肯定是同一个对象了,也就可看到连续输出了。

2. 第二个问题不太熟悉,直接使用 javap -c synTest.class 查看字节码如下,因为 synchronized  进入监视器的字节码指令是 monitorenter ,退出监视器的字节码命令是monitorexit,而这两个命令仅仅出现一次分别是第6行指令和106行指令,所以并没有同步消除优化

public void add();
    Code:
       0: aload_0
       1: getfield      #3                  // Field obj:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter
       7: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
      10: invokestatic  #16                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      13: invokevirtual #17                 // Method java/lang/Thread.getName:()Ljava/lang/String;
      16: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      19: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
      22: new           #19                 // class java/lang/StringBuilder
      25: dup
      26: invokespecial #20                 // Method java/lang/StringBuilder."<init>":()V
      29: invokestatic  #16                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      32: invokevirtual #17                 // Method java/lang/Thread.getName:()Ljava/lang/String;
      35: invokevirtual #21                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      38: ldc           #22                 // String ===
      40: invokevirtual #21                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: aload_0
      44: getfield      #3                  // Field obj:Ljava/lang/Object;
      47: invokevirtual #23                 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      50: invokevirtual #24                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      53: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      56: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
      59: new           #19                 // class java/lang/StringBuilder
      62: dup
      63: invokespecial #20                 // Method java/lang/StringBuilder."<init>":()V
      66: invokestatic  #16                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      69: invokevirtual #17                 // Method java/lang/Thread.getName:()Ljava/lang/String;
      72: invokevirtual #21                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      75: ldc           #25                 // String -
      77: invokevirtual #21                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      80: aload_0
      81: getfield      #3                  // Field obj:Ljava/lang/Object;
      84: invokevirtual #23                 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      87: invokevirtual #24                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      90: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      93: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
      96: invokestatic  #16                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      99: invokevirtual #17                 // Method java/lang/Thread.getName:()Ljava/lang/String;
     102: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     105: aload_1
     106: monitorexit
     107: goto          115
     110: astore_2
     111: aload_1
     112: monitorexit
     113: aload_2

 更多Java并发编程的学习笔记欢迎访问  https://www.zhoutao123.com/page/book/11

第一个问题:前面有人已经回答了,你这里Integer对象++之后,和之前不是同一个对象了,简单验证如下:

执行i++之前:

执行i++之后:

第二个问题:没太明白你说的意思,不过锁消除和锁粗化的意思大概是:
锁消除:经过逃逸分析技术,如果发现不会出现多线程竞争,那么可能进行锁消除优化,典型的是对非共享变量(未逃逸)的操作进行加锁,比如:

public void test() {
        Test test = new Test();
        synchronized (test) {
            test.xxx();
        }
    }

这个test变量存在局部变量表,随栈帧出栈就销毁了,不会出现多线程竞争,没有方法逃逸,更没有线程逃逸,这里的加锁就可以消除

锁粗化:其实在你举的这个例子中才应该进行粗化处理:

先不考虑偏向锁和轻量级锁,这个代码块频繁的加锁-解锁更加影响性能,还不如一个synchronized把和这个代码块都包起来

synchronized锁定的是integer对象实例的引用地址,integer++之后已经指向了另外的地址,锁定的已经不是同一个对象引用了,可以使用一个单独的锁定对象,避免对象地址修改

synchronized (obj) 改为 synchronized (this),详细的解释可以看看这篇文章 http://tutorials.jenkov.com/java-concurrency/synchronized.html

做个总结:

第一个问题的确是因为Integer、Long等这些封装类型进行自增后,引用地址发生变化,多线程同步时实际锁的是不同对象,最后实现同步效果  

第二个问题不重要,是我理解有误,以为第一个问题的结果是因为JVM进行了重排序