在try块内部放置同步块还是在同步块内部放置try块更好?

21

例如,这个更好吗?

try {
    synchronized (bean) {
        // Write something              
    }
} catch (InterruptedException e) {
    // Write something
}

或者这样更好:

synchronized (bean) {
    try {           
        // Write something              
    }
    catch (InterruptedException e) {
        // Write something
    }
}

我想知道哪种方法是最佳实践。显然,考虑到我必须同步try块中的所有代码。我不是在谈论只需同步try块中部分代码的情况(在这种情况下,我认为将同步块放在try内会更好)。 我的疑问是关于需要同步整个try块的情况。

7个回答

14
除非您明确需要在同步块内使用catch,否则我会尽可能地使同步代码段最小,并将其放置在try/catch内部。因此,第一种模式更好。然后,如果您需要在catch中执行操作(例如记录异常或重新中断线程,请参见下文),这些操作不会阻塞其他线程。
话虽如此,如果同步块包含许多行(通常不是一个好主意),那么我会考虑将try/catch块移动到引发异常的方法附近(很可能是wait或notify)。对于大量的行,您可能会出现使用大型try/catch块处理异常的风险。这在一定程度上取决于这里的参考框架。
顺便说一句,确保至少记录中断异常。永远不要忽略它们。您可能还想重新中断线程:
try {
   ...
} catch (InterruptedException e) {
   // always a good pattern
   Thread.currentThread().interrupt();
   // handle the interrupt here by logging or returning or ...
}

如果您确实需要在catch部分执行操作...这些操作不会阻塞其他线程。为什么我们会因为异常而放弃锁呢? - Cratylus
就像@Gray所说的,您必须根据您想要的效果选择,同步捕获块的操作或不同步,并尽可能保持最小的同步块。但是,我认为您必须考虑保留try/catch块,以保护您真正想要保护的代码,避免较大的块。因此,我们可以总结出这两个良好的编程实践:
  1. 保持小的同步块
  2. 保持try/catch块围绕您真正想要保护的代码。
- Jose Renato
为什么因为异常而放弃锁定呢?因为日志调用可能是同步的且开销很大吗?这取决于catch块中的代码@Cratylus。 - Gray
@Gray:你的意思是,如果我们唯一能做的就是记录异常,那么好吧,你说得对。 - Cratylus
@Craylus,如果一个异常阻止了同步块完成其设计的操作,那么锁必须被释放。鉴于在此使用InterruptedException作为示例,对于未指定的操作而言,这种有些通用的异常意味着什么是不清楚的。我得出这个答案是因为我想确保在同步块中抛出并且没有在同步块中捕获的异常在所有情况下都会释放锁。这是我代码所需要的行为。 - Jason

3

并没有最佳实践。这取决于你是否需要在同步块内部处理异常。你可能需要其中之一,并且应该选择使同步块最短的那个,同时仍然保证代码正确和线程安全。


2

您似乎认为这只是一个审美问题,但事实并非如此。这是一个功能性问题,答案由每个具体情况的要求来决定。每个同步块应该足够大,以包含需要同步的所有内容,但不应过大。


0

这个太老了,但今天我正在审查一位初级同事的代码时,发现一个与使用Java Lock对象类似于同步块有关的巨大错误。我正在寻找一些关于Java同步不完美的文档,并偶然发现了这个问题,想留下我的解决方案给那个家伙:

在同步的情况下,它确实取决于您的逻辑,因为Java保证锁的释放。

但是,当您使用基于对象的锁,例如Java的ReentrantLock时,您必须始终执行以下操作:

private final ReentrantLock lock = new ReentrantLock ();
void foo() {
    lock.lock();
    try {
        // Do your things
    } finally {
        lock.unlock();
    }
}

一旦您有了这个结构来确保锁的释放,您可以将锁和整个try/finally封装到另一个try中,如果您想在锁外处理catch,或者将其放在// Do your things处,如果您想从内部处理它。

此外,请记住,Java锁并不完美,就像在任何编程语言中一样,锁的类型选择取决于底层语言、操作的重要性(如果锁失败,是否会有人死亡,或者控制软件重试一百万次就能解决问题?)以及锁的性能影响。

如果您对锁一无所知,我建议您学习以下概念:

  • 监视器(同步)
  • 互斥
  • 读/写锁模式
  • 读-拷贝-更新
  • 信号量(编程)

大多数情况下,您真正需要的是信号量、多读单写锁或仅监视器,因此了解这些术语将使您更快地搜索所需内容。


0

你的catch块是否需要同步?由于你在那里写了“写一些东西”,我假设你将会进行一些日志记录,这不应该需要与良好的日志框架同步,这意味着答案可能是

总的来说,你的目标应该是尽可能少地使用同步。你的同步块越小,你遇到问题的可能性就越小。


0
在这种情况下,InterruptedException 只会发生在 synchronized 块内,您可以在其中调用 waitsleepnotify
通常最佳实践是将 try/catch 块尽可能靠近可能引发异常的代码,以便更容易识别需要修复的代码。

0

这与synchronized{try{}}try{synchronized{}}无关,但与synchronized{catch{}}synchronized{} catch{}有关。它真正取决于您在catch块中执行的操作。

然而,猜测一下,对于InterruptedException,通常应该在synchronized{}之外使用catch{}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接