有人能告诉我同步方法比同步块的优势与一个例子吗?
当前回答
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
}
}
}
其他回答
主要的区别是,如果你使用同步块,你可以锁定一个对象,而不是这个,这允许更灵活。
假设您有一个消息队列和多个消息生产者和消费者。我们不希望生产者相互干扰,但是消费者应该能够检索消息,而不必等待生产者。 我们只需要创建一个对象
Object writeLock = new Object();
从现在开始,每当制作人想要添加一条新信息时,我们就会锁定它:
synchronized(writeLock){
// do something
}
因此,消费者可能仍会阅读,而生产者将被锁定。
大多数情况下,我使用它来同步对列表或映射的访问,但我不想阻止对对象的所有方法的访问。
在下面的代码中,修改列表的线程不会阻塞等待正在修改映射的线程。如果方法在对象上是同步的,那么每个方法都必须等待,即使它们所做的修改不会冲突。
private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();
public void put( String s, Bar b ) {
synchronized( myMap ) {
myMap.put( s,b );
// then some thing that may take a while like a database access or RPC or notifying listeners
}
}
public void hasKey( String s, ) {
synchronized( myMap ) {
myMap.hasKey( s );
}
}
public void add( Foo f ) {
synchronized( myList ) {
myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
}
}
public Thing getMedianFoo() {
Foo med = null;
synchronized( myList ) {
Collections.sort(myList);
med = myList.get(myList.size()/2);
}
return med;
}
关于使用同步块的重要提示:小心你使用的锁对象!
上面user2277816的代码片段说明了这一点,将字符串字面值的引用用作锁定对象。 意识到字符串字面值在Java中是自动互缩的,您应该开始看到问题所在:在字面值“锁”上同步的每段代码都共享同一个锁!这很容易导致完全不相关的代码段发生死锁。
您需要注意的不仅仅是String对象。装箱的原语也是一种危险,因为autoboxing和valueOf方法可以重用相同的对象,这取决于值。
有关更多信息,请参阅: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused
同步方法可以使用反射API进行检查。这对于测试某些契约很有用,比如模型中的所有方法都是同步的。
下面的代码段打印哈希表的所有同步方法:
for (Method m : Hashtable.class.getMethods()) {
if (Modifier.isSynchronized(m.getModifiers())) {
System.out.println(m);
}
}
使用同步块,您可以有多个同步器,因此多个同时但不冲突的事情可以同时进行。
推荐文章
- Intellij IDEA Java类在保存时不能自动编译
- 何时使用Mockito.verify()?
- 在maven中安装mvn到底做什么
- 不可变与不可修改的集合
- 如何在JSON中使用杰克逊更改字段名
- GSON -日期格式
- 如何从线程捕获异常
- 无法解析主机"<URL here>"没有与主机名关联的地址
- 列表是线程安全的吗?
- 如何在Java中打印二叉树图?
- String.format()在Java中格式化双重格式
- com.jcraft.jsch.JSchException: UnknownHostKey
- Java中的操作符重载
- 如何加速gwt编译器?
- 在Hibernate中重新连接分离对象的正确方法是什么?