【问题标题】:Does synchronized (this) lock only the synchronized block or all the "this" code?同步(this)是否仅锁定同步块或所有“this”代码?
【发布时间】:2015-07-10 13:51:26
【问题描述】:
public class ObjectCounter {
    private static long numOfInstances = 0;
    public ObjectCounter(){
        synchronized(this){
        numOfInstances++;
        }
    }
    **public static synchronized long getCount(){
        return numOfInstances;
    }**
//vs//
    **public static long getCount(){
        return numOfInstances;
    }**
}

如果我要运行几个线程,其中一些会调用静态函数getCount(),其中一些会创建新实例。我想在每次调用getCount() 时获取实际实例数。

  1. 代码中的两个选项有区别吗?
  2. 如果我锁定“this”不应该意味着在构造函数退出同步块之前我不能调用getCount()(假设我没有在 getCount() 上写同步)。
  3. 如果我在代码的某个位置执行同步块,它是只锁定同步块还是锁定所有“this”代码?
  4. 从这里开始编辑:谢谢大家,这非常有帮助,但是在您回答之后我还有一些问题。
  5. 如果我理解正确,synchronized(this) 块不会影响(或连接到)静态同步函数(在锁定术语中不是 numOfInstances 增量)?
  6. 是否有更好的选择使增量和 getCount() 函数线程安全? (比如打开一个静态对象并执行 synchronized(obj) 而不是 synchronized(this) - 朋友建议)。
  7. 如果我在 ObjectCounter 类中有一个 f1() 方法(非静态),而一个线程在 synchronized(this) 中,其他线程能否进入 f1() 块(不是同步类或内部有同步块)?
  8. 如果我在 ObjectCounter 中有 f1() 方法(非静态)和 f2() 方法(非静态),则在 f1() 中我有 synchronized(this) 块。当一个线程在 synchronized(this) 块中时,其他线程可以进入 f1() 块(不是同步类或内部有同步块)吗? (假设两个线程在同一个实例上“工作”)

`

【问题讨论】:

  • 在实例的构造函数返回之前,您绝不能允许调用任何对象的实例方法(例如,getCount())。唯一可能的方法是构造函数将this 发布到另一个线程。 (也称为“从构造函数中泄漏 this”)。如果你认为你必须在构造函数中写synchronized(this),那么你可能犯了一个巨大的错误。
  • 在您获得正确且完整的问题答案后,请不要编辑您的问题以添加更多问题(或者,更糟糕的是,替换您现有的问题)。为此提出一个新问题,或在您的后续问题中评论正确答案。
  • 所有的答案都很好而且很有帮助,但是我只能选择一个对我有帮助的,而且我认为只选择一个是不公平的。如果是必须的,我会选择一个。

标签: java multithreading synchronized java-threads synchronized-block


【解决方案1】:

使用synchronized 意味着为了让线程执行该块或方法,它必须获取该块或方法引用(显式或隐式)的锁。对于static synchronized 方法,该锁是类对象上的监视器。对于synchronized(this) 块,使用的锁是当前实例上的监视器。在多个方法或块之间共享锁是强制更新的原子性和内存可见性的原因,共享锁还提供了一个共享通信路径,通过该路径可以进行等待和通知。

由于静态同步块使用的锁与构造函数中的块使用的锁不同,因此进入静态同步块不会被另一个线程访问需要在当前实例上获取锁的块而阻塞,并且同步块在构造函数中对任何东西都没有影响,锁获取将始终是非竞争的。更重要的是,构造函数中的一个线程所做的更改可能不会被使用 getter 的其他线程看到。同步会影响锁定和内存可见性。

这个更改后的版本可以工作:

public class ObjectCounter {
    private static long numOfInstances = 0;
    public ObjectCounter(){
        synchronized(ObjectCounter.class){
            numOfInstances++;
        }
    }
    public static synchronized long getCount(){
        return numOfInstances;
    }
}

因为 getter 和递增块使用相同的锁。让不同的线程获取相同的监视器可确保对计数器的更改安全地发布,以便访问 getter 的另一个线程可以看到更新的值。

synchronized 关键字说,“你必须先获得一个锁才能进入”,其中假定锁的方法是:方法上的 static 关键字是类上的监视器,没有 static 关键字它是监控当前实例。为了使锁定正常工作,不同的块和方法需要使用相同的锁。可以说,Java 的设计方式有太多的语法糖和太多的便利:允许隐式选择锁并将监视器放在 java.lang.Object 上可能会导致混乱。

写下您的问题 #6:对于您在这里所做的事情,最好使用AtomicLong。使用同步块来协调需要在不受其他线程干扰的情况下发生的多个更改。

问题#3、#7 和#8 看起来非常相似:如果方法/块没有尝试获取锁,则没有什么可以阻止线程执行该方法/块。对象作为一个整体没有得到任何保护,使用同步方法或块来强制锁定就是保护。少考虑“使用synchronized 关键字”,多考虑锁线程需要获取的内容。

【讨论】:

  • 坏狗!菜鸟需要了解锁与块相关联,不与方法相关联,也不与变量相关联。我已经记不清有多少次我看到他们问,“两个线程怎么可能同时进入同一个同步块/方法?”或者,“两个线程怎么可能同时在count 上同步?”他们需要非常清楚地说明 synchronized 阻止的唯一事情是两个线程同时锁定同一个 instance
  • @james:我改写了措辞,试图让这一点更清楚。欢迎提出改进或只是编辑的建议。顺便说一句,我的 gravatar 是一只猫,虽然是粗略渲染的。
  • 天哪!我一直以为它是某种雪纳瑞。很抱歉这句话太粗鲁了,但正是你所说的“与块相关的锁”让我走了。有时我可能会对我的同龄人说同样的话,但我的同龄人都知道锁和代码之间的关联是我的程序设计的一个方面,而不是因为语言是如何工作的。
  • @james:np,我认为措辞因此得到了改进,谢谢。
  • @NathanHughes 在不同的锁上同步如何保证可见性? (我知道它不是线程安全的,但是对于不同锁中的线程来说变化是可见的)
【解决方案2】:
  1. 是的,选项有所不同。在上面的选项中,两个线程不能同时调用getCount(),在下面的选项中它们可以。

  2. 是的,没错。同一时间只能有一个线程持有一个对象的锁。

  3. 每个对象都有自己的锁。所以它锁定了该对象的所有 synchronized (this) 块。

但是请注意,每个对象都有自己的锁,每个类也都有自己的锁。在构造函数中,您使用对象锁来访问静态(类)变量,而在getCount() 中,您使用类锁。这意味着您的代码不是线程安全的!

【讨论】:

    【解决方案3】:

    synchronized 步骤:

    1. 检查是否已获取对象锁。如果是这样,请进入同步块/方法
    2. 尝试获取锁定。如果锁已经被另一个线程获取,那么该线程将等待锁被释放,此时它会再次经历循环(2.)

    【讨论】:

      【解决方案4】:

      代码中的两个选项有区别吗?

      是的,有明显的区别。首先,您正在同步线程访问ObjectCounter 的类对象上的getCount() 方法。而你不是。

      如果我锁定“this”不应该意味着我不能调用 getCount() 直到 承包商退出同步块(假设我不写 在 getCount()) 上同步。

      由于一个对象只有一个锁(类锁是不同的,通过使用static 关键字和synchronized 来保持),所以如果某个其他线程正在获取该锁,要么是因为synchronized(this){,要么是因为这个synchronized long getCount(){ 之后,试图获取锁的新线程必须等到前一个线程释放锁。

      现在,在您的情况下,您正在执行static synchronized long getCount(){,因此,它的锁定与synchronized(this){ 不同。这意味着如果某个线程因为synchronized(this){ 而获取锁,而其他线程试图调用getCount(),那么该线程将不会被阻塞。

      如果我在代码中的某个位置执行同步块,它会锁定吗 唯一的同步块还是所有“this”代码?

      1. 非静态同步: 如果你在代码中的某个地方做了同步块并且它是非静态的public synchronized long getCount(){,那么你的对象的锁也会被持有,所以试图获取锁的新线程必须等到前一个线程释放锁。

      2. 静态同步: 如果你在代码的某个地方做了同步块,并且是静态的public static synchronized long getCount(){,那么它对非静态同步的锁没有影响。


      底线:

      • 一个对象只有一个锁,如果该锁被某个线程获取,那么其他线程必须等待直到该锁被释放.

      • 然后有一个类锁,如果static关键字与synchronized关键字一起使用,就会被持有。

      【讨论】:

      • 没有任何解释的投反对票是很差劲的表现。
      • @jameslarge 哦,好的。我深表歉意。
      • @NathanHughes “答案需要快速切中要害。”是的,金句!!! 2个不合格的黑点:(
      • @hagrawal 我查看了您答案的编辑历史记录。你的答案的第一个版本说 所以如果其他线程因为 synchronized(this){ 或因为这个 static synchronized long getCount(){ 正在获取该锁,那么尝试获取锁的新线程必须等待直到前一个线程释放了锁。这就是为什么您可能会被否决,因为这显然是一个错误的陈述。我同意这个投票的反对者。
      • @CKing 我使用 OP 的代码进行解释,并错过了从中删除 static,我后来意识到并更正了。我想我付出了失败的代价。不过谢谢你的注意。我同意你对反对者的立场,但我想我涵盖了 OP 正在寻找的所有内容,尽管经过几次编辑。
      猜你喜欢
      • 2012-02-03
      • 1970-01-01
      • 1970-01-01
      • 2011-05-22
      • 1970-01-01
      • 2014-06-07
      • 2012-06-25
      • 1970-01-01
      相关资源
      最近更新 更多