当前位置:  开发笔记 > 编程语言 > 正文

避免在Java中同步(this)?

如何解决《避免在Java中同步(this)?》经验,为你挑选了9个好方法。

每当有关Java同步的问题出现时,有些人非常渴望指出synchronized(this)应该避免的.相反,他们声称,首选锁定私人参考.

一些给出的原因是:

一些邪恶的代码可能会偷你的锁(这个非常受欢迎,也有一个"意外"变种)

同一类中的所有同步方法使用完全相同的锁,这会降低吞吐量

你(不必要地)暴露了太多的信息

其他人,包括我在内,认为这synchronized(this)是一个被大量使用的习惯用法(也在Java库中),是安全且易于理解的.它不应该被避免,因为你有一个错误,你不知道多线程程序中发生了什么.换句话说:如果适用,则使用它.

我有兴趣看到一些现实世界的例子(没有foobar的东西)避免锁定this是最好的,当synchronized(this)也做的工作.

因此:您是否应始终避免synchronized(this)并使用私有引用上的锁来替换它?


一些进一步的信息(更新为答案):

我们正在谈论实例同步

考虑隐式(synchronized方法)和显式形式synchronized(this)

如果您引用Bloch或其他有关该主题的权限,请不要遗漏您不喜欢的部分(例如,有效Java,线程安全项目:通常它是实例本身的锁,但也有例外.)

如果你需要锁定的粒度而不是synchronized(this)提供,那么synchronized(this)不适用,所以这不是问题

Darron.. 125

我将分别介绍每一点.

    一些邪恶的代码可能会偷你的锁(这个非常受欢迎,也有一个"意外"变种)

    不小心担心.它相当于这种使用this是你的类暴露界面的一部分,应该记录在案.有时需要其他代码使用锁的能力.对于类似的事情Collections.synchronizedMap(见javadoc)也是如此.

    同一类中的所有同步方法使用完全相同的锁,这会降低吞吐量

    这是过于简单化的思维; 只是摆脱synchronized(this)不会解决问题.适当的吞吐量同步需要更多考虑.

    您(不必要地)暴露了太多信息

    这是#1的变体.使用synchronized(this)是您界面的一部分.如果您不希望/需要暴露,请不要这样做.

基本上同步(this)*被暴露,因为这意味着外部代码会影响您的类的操作.所以我断言你必须将它记录为界面,即使语言没有. (65认同)

类似.请参阅Javadoc for Collections.synchronizedMap() - 返回的对象在内部使用synchronized(this),并且它们希望使用者利用它来对迭代等大规模原子操作使用相同的锁. (15认同)

实际上,Collections.synchronizedMap()在内部未使用synced(this),而是使用了私有的最终锁定对象。 (2认同)


cletus.. 84

那么,首先应该指出:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

在语义上等同于:

public synchronized void blah() {
  // do stuff
}

这是不使用的一个原因synchronized(this).你可能会争辩说你可以在synchronized(this)街区附近做点什么.通常的原因是尝试避免必须进行同步检查,这会导致各种并发问题,特别是双重检查锁定问题,这只是表明进行相对简单的检查是多么困难.线程安全的.

私人锁定是一种防御机制,这绝不是一个坏主意.

此外,正如您所提到的,私有锁可以控制粒度.对象上的一组操作可能与另一组完全无关,但synchronized(this)会相互排除对所有操作的访问.

synchronized(this) 只是真的不给你任何东西.



1> Darron..:

我将分别介绍每一点.

    一些邪恶的代码可能会偷你的锁(这个非常受欢迎,也有一个"意外"变种)

    不小心担心.它相当于这种使用this是你的类暴露界面的一部分,应该记录在案.有时需要其他代码使用锁的能力.对于类似的事情Collections.synchronizedMap(见javadoc)也是如此.

    同一类中的所有同步方法使用完全相同的锁,这会降低吞吐量

    这是过于简单化的思维; 只是摆脱synchronized(this)不会解决问题.适当的吞吐量同步需要更多考虑.

    您(不必要地)暴露了太多信息

    这是#1的变体.使用synchronized(this)是您界面的一部分.如果您不希望/需要暴露,请不要这样做.


基本上同步(this)*被暴露,因为这意味着外部代码会影响您的类的操作.所以我断言你必须将它记录为界面,即使语言没有.
类似.请参阅Javadoc for Collections.synchronizedMap() - 返回的对象在内部使用synchronized(this),并且它们希望使用者利用它来对迭代等大规模原子操作使用相同的锁.
实际上,Collections.synchronizedMap()在内部未使用synced(this),而是使用了私有的最终锁定对象。

2> cletus..:

那么,首先应该指出:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

在语义上等同于:

public synchronized void blah() {
  // do stuff
}

这是不使用的一个原因synchronized(this).你可能会争辩说你可以在synchronized(this)街区附近做点什么.通常的原因是尝试避免必须进行同步检查,这会导致各种并发问题,特别是双重检查锁定问题,这只是表明进行相对简单的检查是多么困难.线程安全的.

私人锁定是一种防御机制,这绝不是一个坏主意.

此外,正如您所提到的,私有锁可以控制粒度.对象上的一组操作可能与另一组完全无关,但synchronized(this)会相互排除对所有操作的访问.

synchronized(this) 只是真的不给你任何东西.


我一般不同意"X是一种防御机制,这绝不是一个坏主意." 由于这种态度,有很多不必要的膨胀代码.
您可以防止外部对象意外(或恶意)锁定"此".
我完全不同意这个答案:应该在最短的时间内保持锁定,这正是你想要在同步块周围"做东西"而不是同步整个方法的原因.
做同步块之外的东西总是善意的.重点是人们在很多时候都会弄错,甚至没有意识到这一点,就像双重检查锁定问题一样.地狱之路铺好了意图.
"同步(这个)真的不会给你任何东西." 好吧,我用同步替换它(myPrivateFinalLock).这给了我什么?你说它是一种防御机制.我受到什么保护?
@finnw:我同意臃肿的代码,但是当你不确定时,最好是安全(臃肿)而不是抱歉.

3> Andreas Baku..:

当您使用synchronized(this)时,您正在使用类实例作为锁本身.这意味着当线程1获取锁定时,线程2应该等待

假设以下代码

public void method1() {
    // do something ...
    synchronized(this) {
        a ++;      
    }
    // ................
}


public void method2() {
    // do something ...
    synchronized(this) {
        b ++;      
    }
    // ................
}

方法1修改变量a和方法2修改变量b,应该避免两个线程同时修改同一个变量.但是当thread1修改athread2修改b时,它可以在没有任何竞争条件的情况下执行.

不幸的是,上面的代码不允许这样做,因为我们对锁使用相同的引用; 这意味着线程即使它们不处于竞争状态也应该等待,显然代码牺牲了程序的并发性.

解决方案是为两个不同的变量使用2个不同的锁.

public class Test {

    private Object lockA = new Object();
    private Object lockB = new Object();

    public void method1() {
        // do something ...
        synchronized(lockA) {
            a ++;      
        }
        // ................
    }


    public void method2() {
        // do something ...
        synchronized(lockB) {
            b ++;      
        }
        // ................
    }

}

上面的例子使用了更细粒度的锁(2个锁而不是一个(分别是变量ab的lockAlockB),因此结果允许更好的并发性,另一方面它变得比第一个例子更复杂......


为了有一个死锁,我们应该从A同步的块执行调用到由B.daveb同步的块,你错了......
"synchronized(this)"未提供的粒度超出了我的问题的范围.你的锁定领域不应该是最终的吗?
据我所知,这个例子中没有死锁.我接受它只是伪代码但我会使用java.util.concurrent.locks.Lock的一个实现,如java.util.concurrent.locks.ReentrantLock

4> Olivier..:

虽然我同意不盲目地遵守教条规则,但"锁定窃取"场景对您来说是否如此古怪?一个线程确实可以获取对象"external"(synchronized(theObject) {...})的锁定,阻塞等待同步实例方法的其他线程.

如果您不相信恶意代码,请考虑此代码可能来自第三方(例如,如果您开发某种应用程序服务器).

"意外"版本似乎不太可能,但正如他们所说,"做一些傻逼的东西,有人会发明一个更好的白痴".

所以我同意它取决于什么是课堂的思想学派.


编辑以下eljenso的前3条评论:

我从未经历过锁定窃取问题,但这是一个想象的场景:

假设您的系统是一个servlet容器,我们正在考虑的对象就是ServletContext实现.它的getAttribute方法必须是线程安全的,因为上下文属性是共享数据; 所以你声明它synchronized.我们还假设您根据容器实现提供公共托管服务.

我是您的客户,并在您的网站上部署我的"好"servlet.碰巧我的代码包含了一个调用getAttribute.

黑客伪装成另一个客户,在您的网站上部署他的恶意servlet.它在init方法中包含以下代码:

synchronized (this.getServletConfig().getServletContext()) {
   while (true) {}
}

假设我们共享相同的servlet上下文(只要这两个servlet位于同一个虚拟主机上,那么规范允许),我的调用getAttribute将永久锁定.黑客在我的servlet上实现了DoS.

如果getAttribute在私有锁上同步,则无法进行此攻击,因为第三方代码无法获取此锁.

我承认这个例子是人为的,并且对servlet容器的工作方式过于简单化了,但恕我直言,它证明了这一点.

所以我会根据安全考虑做出我的设计选择:我是否可以完全控制可以访问实例的代码?线程无限期地锁定一个实例的后果是什么?


是的,锁窃窃的情况似乎对我来说很遥远.每个人都提到它,但实际上是谁或经历过它?如果你"意外地"锁定了一个你不应该锁定的对象,那么就有这种情况的名称:这是一个错误.修理它.
此外,锁定内部引用并不是没有"外部同步攻击":如果您知道代码的某个同步部分等待外部事件发生(例如文件写入,DB中的值,计时器事件),您可能也安排它阻止.

5> serg10..:

在C#和Java阵营中似乎有不同的共识. 我见过的大多数Java代码都使用:

// apply mutex to this instance
synchronized(this) {
    // do work here
}

而大多数C#代码选择更可靠:

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

C#成语当然更安全.如前所述,不能从实例外部对锁进行恶意/意外访问.Java代码也存在这种风险,但似乎Java社区已经越来越倾向于稍微不那么安全但稍微简洁的版本.

这并不意味着对Java的挖掘,只是反映了我在两种语言上工作的经验.


也许因为C#是一种较年轻的语言,他们从Java阵营中出现的糟糕模式中学到了这样的代码并且更好吗?还有更少的单身人士吗?:)
呵呵.很可能是真的,但我不会上升到诱饵!有人认为我可以肯定地说C#代码中有更多的大写字母;)

6> Rohit Singh..:

这取决于实际情况.
如果只有一个共享实体或多个共享实体.

请参阅 此处的完整工作示例

一个小介绍.

线程和可共享实体
多个线程可以访问同一个实体,例如,多个connectionThread共享一个messageQueue.由于线程同时运行,可能有可能将一个数据覆盖另一个数据,这可能是混乱的情况.
因此,我们需要某种方法来确保一次只能通过一个线程访问可共享实体.(CONCURRENCY).

同步块
synchronized()块是一种确保可共享实体的并发访问的方法.
首先,一个小类比
假设洗手间内有一个双人P1,P2(螺纹)洗脸盆(可共享的实体),还有一扇门(锁).
现在我们希望一个人一次使用洗脸盆.
一种方法是在门被锁定时通过P1锁定门P2等待直到p1完成他的工作
P1解锁门
然后只有p1可以使用洗脸盆.

句法.

synchronized(this)
{
  SHARED_ENTITY.....
}

"this"提供了与类关联的内部锁(Java开发人员设计的Object类,使得每个对象都可以作为监视器工作).当只有一个共享实体和多个线程(1:N)时,上述方法可以正常工作.
在此输入图像描述 N个可共享实体-M个线程
现在想想洗手间内只有一个洗脸盆而且只有一个门的情况.如果我们使用之前的方法,只有p1可以一次使用一个洗脸盆,而p2会在外面等待.由于没有人使用B2(洗脸盆),这是资源的浪费.
一个更明智的方法是在洗手间内创建一个较小的房间,并为每个洗脸盆提供一扇门.这样,P1可以访问B1,P2可以访问B2,反之亦然.

washbasin1;  
washbasin2;

Object lock1=new Object();
Object lock2=new Object();

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }

在此输入图像描述
在此输入图像描述

在线程上查看更多信息 ----> 这里



7> jamesh..:

java.util.concurrent软件包大大降低了我的线程安全代码的复杂性.我只有轶事证据可以继续,但我见过的大部分工作synchronized(x)似乎都是重新实现Lock,Semaphore或Latch,而是使用较低级别的监视器.

考虑到这一点,使用任何这些机制进行同步类似于在内部对象上进行同步,而不是泄漏锁.这是有益的,因为您可以绝对确定您通过两个或多个线程控制进入监视器的条目.



8> Ravindra bab..:

    如果可能,使数据不可变(final变量)

    如果无法避免跨多个线程共享数据的突变,请使用高级编程构造[例如粒度 LockAPI]

锁提供对共享资源的独占访问:一次只有一个线程可以获取锁,并且对共享资源的所有访问都需要首先获取锁.

ReentrantLock实现Lock接口的示例代码

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

锁定同步的优点(这)

    同步方法或语句的使用强制所有锁获取和释放以块结构方式发生.

    通过提供,锁实现提供了使用同步方法和语句的附加功能

      获取锁定的非阻塞尝试(tryLock())

      尝试获取可以中断的锁(lockInterruptibly())

      尝试获取可以超时(tryLock(long, TimeUnit))的锁定.

    Lock类还可以提供与隐式监视器锁完全不同的行为和语义,例如

      保证订购

      非重复使用

      死锁检测

看看有关各种类型的SE问题Locks:

同步与锁定

您可以使用高级并发API而不是Synchronied块来实现线程安全.本文档页面提供了良好的编程结构,以实现线程安全.

锁定对象支持锁定习惯用法,简化了许多并发应用程序.

执行程序定义用于启动和管理线程的高级API.java.util.concurrent提供的执行程序实现提供适用于大规模应用程序的线程池管理.

并发集合使管理大量数据更容易,并且可以大大减少同步需求.

Atomic Variables具有最小化同步并有助于避免内存一致性错误的功能.

ThreadLocalRandom(在JDK 7中)提供从多个线程有效生成伪随机数.

对于其他编程结构,也请参阅java.util.concurrent和java.util.concurrent.atomic包.



9> Neil Coffey..:

如果您已经决定:

你需要做的就是锁定当前对象; 和

你想用比整个方法小的粒度锁定它;

然后我没有看到同步的禁忌(这个).

有些人故意在方法的整个内容中使用synchronized(this)(而不是将方法标记为同步),因为他们认为对读者"更清楚"哪个对象实际上是同步的.只要人们做出明智的选择(例如,通过这样做,他们实际上是在方法中插入额外的字节码,这可能会对潜在的优化产生连锁效应),我并不特别看到这个问题. .你应该总是记录你的程序的并发行为,所以我没有看到"'synchronized'发布行为"的论点是如此引人注目.

至于你应该使用哪个对象的锁定的问题,我认为在当前对象上进行同步是没有问题的,如果你正在做的事情的逻辑以及你的类通常如何被使用的话.例如,对于集合,逻辑上期望锁定的对象通常是集合本身.

推荐阅读
mobiledu2402851173
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有