【问题标题】:Couple of questions regarding the synchronized keyword in Java (and C#'s lock)关于 Java 中的 synchronized 关键字(和 C# 的锁)的几个问题
【发布时间】:2011-01-04 08:54:19
【问题描述】:
  1. 以下类在 Java 中是否等效?如果不是,为什么?

    class Abc {
        private int c = 0;
    
    
    
    public synchronized void add(int a)  {
        c += a;
    }
    
    
    public synchronized void subtract(int a) {
        c -= a;
    
    } } class Def { private int c = 0; private final Object lock = new Object();
    public void add(int a)  {
        synchronized(lock) {
             c += a;
        }
    }
    
    
    public void subtract(int a) {
        synchronized(lock) {
            c -= a;
        }
    
    } }
  2. 另外,在Def 中,使用this 作为同步参数而不是lock 会有什么问题?是以下问题吗?

    Def def = new Def()
    synchronized (def) {
        def.add(5); //originates deadlock? or is this allowed AS LONG
                    //as all this happens in the same thread?
    }
    
  3. Java 中的同步语句是否与 C# 的锁定语句一样?如果不是,它们的区别是什么?如果是,为什么 C# 也不允许像 Java 允许的那样锁定方法?

  4. 所以我猜synchronized (this) 的问题可以用下面的例子来描述?

    class Xyz {
        private int c;
    
    
    
            public void add(int a)  {
                synchronized(this) {
                     c += a;
                }
            }
    
    
            public void subtract(int a) {
                synchronized(this) {
                    c -= a;
                }
           }
    
    
           public void show666() {
               return 666;
           }
    
    }

    Thread1 调用xyz.add(0),同时Thread2 尝试调用xyz.show666()。 Thread2 必须等待 Thread1 以xyz.add(0) 结束,尽管如此 它不需要任何与锁直接相关的信息。是这样吗?

【问题讨论】:

    标签: c# java multithreading synchronized


    【解决方案1】:

    1 以下类在 Java 中是否等效?如果不是,为什么?

    它们在同步的实例上有所不同,因此受到这些实例之间差异的影响。

    Abc 类使用调用该方法的 Abc 实例。其他有权访问该实例的代码也可以通过在同步块中显式使用该实例来使用它进行同步。

    Def 类中的同步代码块明确命名了它们与之同步的实例。由于此实例是 Def 实例私有的,Def 外部的代码无法使用它进行同步(除非该实例以某种方式泄露)。

    有些人可能会争辩说Def 方法更安全,因为与应该使用私有实例变量相同的原因,封装锁很重要。

    最后,synchronized关键字和使用synchronized语句是有区别的,e.g.同步语句可以锁定任何对象,而不仅仅是执行代码的实例,同步关键字的效率略高,等等。

    2a 另外,在 Def 中使用 this 作为同步参数而不是 lock 会有什么问题?

    没问题。在实例方法上使用 synchronized 关键字在语义上等同于将方法的代码包装在同步块中,并在 this 上同步(synchronized 关键字效率稍高一些)。在静态方法上使用 synchronized 关键字与在类本身上使用同步块相同(例如 synchronized(FooBar.class) { ... })。

    2b 是以下问题吗?

    不,Java 中的锁是可重入的,这只是意味着持有保护实例上的锁的线程可以进入和退出在同一实例上同步的任何其他代码块。

    3a Java 中的同步语句是否与 C# 的锁定语句一样?如果不是,它们的区别是什么?

    语义等价。

    http://en.csharp-online.net/CSharp_FAQ:_What_is_the_difference_between_CSharp_lock_and_Java_synchronized#Synchronized_code_blocks

    但请注意关于Monitor.EnterMonitor.Exit 的答案

    Are there any differences between Java's "synchronized" and C#'s "lock"?

    3b 如果是,为什么 C# 也不允许锁定方法,就像 Java 允许的那样?

    确实如此 - 使用 [MethodImpl(MethodImplOptions.Synchronized)] 注释。

    http://en.csharp-online.net/CSharp_FAQ:_What_is_the_difference_between_CSharp_lock_and_Java_synchronized#Synchronized_methods

    4 Thread1 调用 xyz.add(0),同时 Thread2 尝试调用 xyz.show666()。 Thread2 必须等待 Thread1 使用 xyz.add(0) 完成,尽管它不需要任何与锁直接相关的信息。是这样吗?

    不,很多人认为“锁定”一个实例会影响整个实例。它只影响在该实例上同步的同步代码(同步语句中的同步方法和代码)。未同步的代码不受影响(至少在它遇到同步语句或调用同步方法之前)。

    不同步的方法show666() 不会导致线程阻塞。如果将synchronized(this) 语句更改为synchronized 方法,则不会发生任何变化——同样show666 不会阻塞(除非它也同步)。

    【讨论】:

      【解决方案2】:
      1. 不,它们是不同的。 using synchronized(reference){} 获取给定引用(您的锁对象)上的监视器锁,并在方法声明中使用 synchronized 使用 this 作为监视器锁。最终结果是外部调用者无法获得与addsubtract 方法相同的锁。

      2. 不会出现死锁,您可以持有任意数量的监视器锁,另外Def 对象使用不同的对象进行锁定。但即使是头等舱Abc也不会死锁。 java中的同步锁是可重入的。因此,理论上您可以尝试多次将它们锁定在同一个线程上。

      3. 令人惊讶的是,C# 不允许在方法上使用 lock,但在引用上使用时,如果与 java synchronized 关键字不同,它似乎确实相似。 C# 的设计目标之一是足够熟悉 Java 开发人员能够在不完全替换思想的情况下使用它,所以我想这并不太令人惊讶。

      【讨论】:

      • 将同步(锁定)更改为同步(此)会使它们等效吗?
      【解决方案3】:

      http://download.oracle.com/javase/tutorial/essential/concurrency/locksync.html

      它应该可以帮助您回答 1 和 2,特别是它指出:

      重入同步

      回想一下,线程无法获取 另一个线程拥有的锁。但是一个 线程可以获取它的锁 已经拥有。允许线程 多次获取同一个锁 启用可重入同步。 这描述了一种情况,其中 同步代码,直接或 间接地,调用一个方法,该方法也 包含同步代码,并且两者 代码集使用相同的锁。 没有可重入同步, 同步代码将不得不采取 许多额外的预防措施,以避免 有一个线程会导致自己阻塞。

      1. 据我了解,除非锁被其他人同步,否则这两个类从外部看几乎是等价的。

      2. 由于可重入同步,不会引起任何特殊问题

      (3.不熟悉C#)

      【讨论】:

        【解决方案4】:

        1.一般来说,它们是不同的,因为同步模块中使用了不同对象的监视器。顺便说一句,如果您不使用 Abc 类实例的监视器(特别是,如果您不在 abc 类之外使用 synchronized(abcObject){},其中 abcObject 是 Abc 类的实例),这两个类的行为将相同

        2.a.以下两种方法是等价的:

        public void add(int a)  {
            synchronized(this) {
                 c += a;
            }
        }
        

        public synchronized void add(int a)  {
                 c += a;
        }
        

        2.b。我在该代码中没有看到任何问题:两个监视器(def 实例的监视器和 def.lock 实例的监视器)总是按严格的顺序锁定(没有循环等待条件)

        3.C#什么都不能说

        【讨论】:

          【解决方案5】:
          1. 它们几乎是等价的,只有一点点区别:因为Def 使用内部私有对象作为锁,所以没有其他人可以从外部世界锁定该对象。在Abc 对象的情况下,其他人可能会锁定该对象,然后从不同的线程调用它的一些方法,这可能会导致死锁。实际可能性很小(因为它显然需要代表另一个程序员的一些恶作剧或无知),但不是零。因此,为了安全起见,有些人更喜欢 Def 样式,尽管 AFAIK 的常见习语与 Abc 相同。

          2. Java 锁是可重入的,因此从同一个线程多次调用锁是可以的。

          3. 抱歉,我不擅长 C#。

          4. 您是说代码示例中的 synchronized(this) 而不是 synchronized(lock) 吗?无论如何,由于show666 未同步,因此对它的调用不会阻塞,即使对同一个对象的同步方法的另一个调用阻塞。

          【讨论】:

          • 1.所以如果 Def 定义了 synchronized(this) 而不是 synchronized(lock) 它们是等价的?
          • 功能上是的,不确定字节码是否完全相同
          • 谢谢。考虑到 java 的锁是可重入的,你能告诉我在什么情况下做 lock(this) 是一件坏事吗?
          • 我猜 syncronized(this) 只是“糟糕”(不能说它真的很糟糕),因为它可以用方法上的同步关键字替换.. 少了一组 {}。有些人更喜欢 synchronized(this) 因为它可以防止锁定的细节泄漏到对象的公共接口中,这有点个人喜好。
          • 不知道这个类使用锁的类的客户端会不会出现问题?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-06-17
          • 1970-01-01
          • 2015-05-10
          • 2015-06-08
          • 1970-01-01
          相关资源
          最近更新 更多