wushu15092
2021-03-05 11:19
采纳率: 100%
浏览 125
已采纳

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会对同步的代码块进行同步消除优化?实际的代码应该是和下图所示?

  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

5条回答 默认 最新

  • 燕归来兮_ 2021-03-05 12:00
    已采纳

    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

    1 打赏 评论
  • 黄智霖-blog 2021-03-05 15:10

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

    执行i++之前:

    执行i++之后:

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

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

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

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

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

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

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

    打赏 评论
  • wushu15092 2021-03-05 18:33

    做个总结:

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

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

    打赏 评论

相关推荐 更多相似问题