有人能告诉我同步方法比同步块的优势与一个例子吗?
当前回答
In general these are mostly the same other than being explicit about the object's monitor that's being used vs the implicit this object. One downside of synchronized methods that I think is sometimes overlooked is that in using the "this" reference to synchronize on you are leaving open the possibility of external objects locking on the same object. That can be a very subtle bug if you run into it. Synchronizing on an internal explicit Object or other existing field can avoid this issue, completely encapsulating the synchronization.
其他回答
TLDR;不要使用synchronized修饰符或synchronized(this){…}表达式but synchronized(myLock){…其中myLock是一个持有私有对象的最终实例字段。
在方法声明中使用synchronized修饰符与在方法主体中使用synchronized(..){}表达式的区别如下:
The synchronized modifier specified on the method's signature is visible in the generated JavaDoc, is programmatically determinable via reflection when testing a method's modifier for Modifier.SYNCHRONIZED, requires less typing and indention compared to synchronized(this) { .... }, and (depending on your IDE) is visible in the class outline and code completion, uses the this object as lock when declared on non-static method or the enclosing class when declared on a static method. The synchronized(...){...} expression allows you to only synchronize the execution of parts of a method's body, to be used within a constructor or a (static) initialization block, to choose the lock object which controls the synchronized access.
然而,使用synchronized修饰符或synchronized(…){…}使用this作为锁对象(如synchronized(this){…}),也有同样的缺点。两者都使用它自己的实例作为锁对象进行同步。这是很危险的,因为不仅对象本身,而且任何其他持有该对象引用的外部对象/代码也可以将其用作同步锁,这可能会产生严重的副作用(性能下降和死锁)。
因此,最佳实践是既不使用synchronized修饰符,也不使用synchronized(…)表达式作为锁对象,而是使用该对象的私有锁对象。例如:
public class MyService {
private final lock = new Object();
public void doThis() {
synchronized(lock) {
// do code that requires synchronous execution
}
}
public void doThat() {
synchronized(lock) {
// do code that requires synchronous execution
}
}
}
您也可以使用多个锁对象,但是需要特别注意,以确保在嵌套使用时不会导致死锁。
public class MyService {
private final lock1 = new Object();
private final lock2 = new Object();
public void doThis() {
synchronized(lock1) {
synchronized(lock2) {
// code here is guaranteed not to be executes at the same time
// as the synchronized code in doThat() and doMore().
}
}
public void doThat() {
synchronized(lock1) {
// code here is guaranteed not to be executes at the same time
// as the synchronized code in doThis().
// doMore() may execute concurrently
}
}
public void doMore() {
synchronized(lock2) {
// code here is guaranteed not to be executes at the same time
// as the synchronized code in doThis().
// doThat() may execute concurrently
}
}
}
唯一真正的区别是同步块可以选择在哪个对象上同步。同步方法只能使用'this'(或同步类方法的相应Class实例)。例如,它们在语义上是等价的:
synchronized void foo() {
...
}
void foo() {
synchronized (this) {
...
}
}
后者更灵活,因为它可以竞争任何对象(通常是成员变量)的关联锁。它也更细粒度,因为您可以在块之前和块之后执行并发代码,但仍然在方法中。当然,您也可以通过将并发代码重构为单独的非同步方法来轻松地使用同步方法。使用任何使代码更容易理解的方法。
同步的方法
优点:
您的IDE可以指示同步方法。 语法更加紧凑。 强制将同步块分割为单独的方法。
缺点:
与此同步,因此外部人员也可以与之同步。 将代码移到同步块之外更加困难。
同步块
优点:
允许为锁使用私有变量,从而将锁强制留在类内部。 同步块可以通过搜索变量的引用来找到。
缺点:
语法更复杂,因此使代码更难阅读。
就我个人而言,我更喜欢使用同步方法,类只关注需要同步的东西。这样的类应该尽可能小,所以应该很容易检查同步。其他人不需要关心同步。
唯一的区别是:同步块允许颗粒状锁定,不像同步方法
基本上同步块或方法被用来编写线程安全的代码,以避免内存不一致的错误。
这个问题很老了,在过去的7年里,很多事情都发生了变化。 为了线程安全,引入了新的编程结构。
您可以通过使用高级并发API而不是同步块来实现线程安全。该文档页提供了实现线程安全的良好编程结构。
锁对象支持简化许多并发应用程序的锁定习惯用法。
executor为启动和管理线程定义了高级API。concurrent提供的执行器实现提供了适合大型应用程序的线程池管理。
并发集合使管理大型数据集合变得更容易,并且可以大大减少同步的需要。
原子变量具有最小化同步和帮助避免内存一致性错误的特性。
ThreadLocalRandom(在JDK 7中)提供了从多个线程有效生成伪随机数的功能。
更好的synchronized替代品是ReentrantLock,它使用Lock API
一个可重入互斥锁,其基本行为和语义与使用同步方法和语句访问的隐式监视锁相同,但具有扩展功能。
锁的例子:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
其他编程结构也可以参考java.util.concurrent和java.util.concurrent.atomic包。
参考这个相关的问题:
同步vs锁定
使用同步块,您可以有多个同步器,因此多个同时但不冲突的事情可以同时进行。
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 自动化invokerrequired代码模式
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder