任何人都可以通过一个例子告诉我同步方法优于synchronized块的优势吗?
任何人都可以通过一个例子告诉我同步方法优于synchronized块的优势吗?谢谢.
在块上使用同步方法没有明显的优势.
也许唯一的一个(但我不称之为优势)是你不需要包含对象引用this
.
方法:
public synchronized void method() { // blocks "this" from here.... ... ... ... } // to here
块:
public void method() { synchronized( this ) { // blocks "this" from here .... .... .... .... } // to here... }
看到?没有任何优势.
块确实比方法有优势,主要是灵活性,因为你可以使用另一个对象作为锁,而同步方法会锁定整个对象.
相比:
// locks the whole object ... private synchronized void someInputRelatedWork() { ... } private synchronized void someOutputRelatedWork() { ... }
与
// Using specific locks Object inputLock = new Object(); Object outputLock = new Object(); private void someInputRelatedWork() { synchronized(inputLock) { ... } } private void someOutputRelatedWork() { synchronized(outputLock) { ... } }
此外,如果方法增长,您仍然可以将同步部分分开:
private void method() { ... code here ... code here ... code here synchronized( lock ) { ... very few lines of code here } ... code here ... code here ... code here ... code here }
唯一真正的区别是同步块可以选择它同步的对象.synchronized方法只能使用'this'
(或同步类方法的相应Class实例).例如,这些在语义上是等价的:
synchronized void foo() { ... } void foo() { synchronized (this) { ... } }
后者更灵活,因为它可以竞争任何对象的关联锁,通常是成员变量.它也更精细,因为您可以在块之前和之后执行并发代码,但仍然在方法内.当然,您可以通过将并发代码重构为单独的非同步方法来轻松使用同步方法.使用哪个使代码更易于理解.
优点:
您的IDE可以指示同步的方法.
语法更紧凑.
强制将同步块拆分为单独的方法.
缺点:
与此同步,因此外人也可以同步它.
在同步块之外移动代码更加困难.
优点:
允许使用私有变量进行锁定,从而强制锁定保留在类中.
通过搜索对变量的引用可以找到同步块.
缺点:
语法更复杂,因此使代码更难阅读.
就个人而言,我更喜欢使用同步方法,而类只关注需要同步的东西.这样的类应该尽可能小,因此应该很容易查看同步.其他人不应该关心同步.
主要的区别是,如果使用一个同步块可能会比其他的物体上锁定这个这允许更加灵活.
假设您有一个消息队列和多个消息生成者和使用者.我们不希望生产者互相干扰,但消费者应该能够在不必等待生产者的情况下检索消息.所以我们只创建一个对象
Object writeLock = new Object();
从现在开始,每当制作人想要添加新消息时,我们就会锁定:
synchronized(writeLock){ // do something }
所以消费者仍然可以阅读,生产者将被锁定.
同步方法
同步方法有两个效果.
首先,当一个线程正在为对象执行同步方法时,所有其他线程调用同一对象的同步方法(暂停执行),直到第一个线程完成对象为止.
其次,当同步方法退出时,它会自动与同一对象的同步方法的任何后续调用建立先发生关系.这可以保证对所有线程都可以看到对象状态的更改.
请注意,构造函数无法同步 - 将synchronized关键字与构造函数一起使用是一种语法错误.同步构造函数没有意义,因为只有创建对象的线程在构造时才能访问它.
同步声明
与synchronized方法不同,synchronized语句必须指定提供内部锁的对象:我经常使用它来同步对列表或映射的访问,但我不想阻止访问对象的所有方法.
问:内部锁定和同步同步是围绕称为内部锁定或监视器锁定的内部实体构建的.(API规范通常将此实体简称为"监视器".)内部锁在同步的两个方面都发挥作用:强制对对象状态进行独占访问,并建立对可见性至关重要的先发生关系.
每个对象都有一个与之关联的内在锁.按照惯例,需要对对象字段进行独占和一致访问的线程必须在访问对象之前获取对象的内部锁,然后在完成它们时释放内部锁.据说一个线程在获得锁定和释放锁定之间拥有内在锁定.只要一个线程拥有一个内部锁,没有其他线程可以获得相同的锁.另一个线程在尝试获取锁时将阻塞.
package test; public class SynchTest implements Runnable { private int c = 0; public static void main(String[] args) { new SynchTest().test(); } public void test() { // Create the object with the run() method Runnable runnable = new SynchTest(); Runnable runnable2 = new SynchTest(); // Create the thread supplying it with the runnable object Thread thread = new Thread(runnable,"thread-1"); Thread thread2 = new Thread(runnable,"thread-2"); // Here the key point is passing same object, if you pass runnable2 for thread2, // then its not applicable for synchronization test and that wont give expected // output Synchronization method means "it is not possible for two invocations // of synchronized methods on the same object to interleave" // Start the thread thread.start(); thread2.start(); } public synchronized void increment() { System.out.println("Begin thread " + Thread.currentThread().getName()); System.out.println(this.hashCode() + "Value of C = " + c); // If we uncomment this for synchronized block, then the result would be different // synchronized(this) { for (int i = 0; i < 9999999; i++) { c += i; } // } System.out.println("End thread " + Thread.currentThread().getName()); } // public synchronized void decrement() { // System.out.println("Decrement " + Thread.currentThread().getName()); // } public int value() { return c; } @Override public void run() { this.increment(); } }
使用同步方法,阻止和不同步交叉检查不同的输出.
注意:静态同步方法和块对Class对象起作用.
public class MyClass { // locks MyClass.class public static synchronized void foo() { // do something } // similar public static void foo() { synchronized(MyClass.class) { // do something } } }
当java编译器将源代码转换为字节代码时,它会以非常不同的方式处理同步方法和同步块.
当JVM执行synchronized方法时,执行线程识别方法的method_info结构设置了ACC_SYNCHRONIZED标志,然后它自动获取对象的锁,调用方法并释放锁.如果发生异常,则线程会自动释放锁.
同步的方法块,而另一方面,绕过JVM的内置支持采集对象的锁,异常处理,并要求该功能可以明确地写在字节码.如果您读取具有同步块的方法的字节代码,您将看到十几个额外的操作来管理此功能.
这显示了生成同步方法和同步块的调用:
public class SynchronizationExample { private int i; public synchronized int synchronizedMethodGet() { return i; } public int synchronizedBlockGet() { synchronized( this ) { return i; } } }
该synchronizedMethodGet()
方法生成以下字节代码:
0: aload_0 1: getfield 2: nop 3: iconst_m1 4: ireturn
这是synchronizedBlockGet()
方法中的字节代码:
0: aload_0 1: dup 2: astore_1 3: monitorenter 4: aload_0 5: getfield 6: nop 7: iconst_m1 8: aload_1 9: monitorexit 10: ireturn 11: astore_2 12: aload_1 13: monitorexit 14: aload_2 15: athrow
同步方法和块之间的一个显着区别是,同步块通常会减小锁定范围.由于锁定范围与性能成反比,因此最好只锁定关键的代码段.使用synchronized块的最好例子之一是在Singleton模式中进行双重检查锁定,而不是锁定整个getInstance()
方法,我们只锁定用于创建Singleton实例的关键代码段.这大大提高了性能,因为锁定只需要一到两次.
使用同步方法时,如果混合静态同步和非静态同步方法,则需要格外小心.
通常我使用它来同步对列表或映射的访问,但我不想阻止访问该对象的所有方法.
在以下代码中,修改列表的一个线程不会阻止等待正在修改映射的线程.如果方法在对象上同步,则每个方法都必须等待,即使它们正在进行的修改不会发生冲突.
private ListmyList = new ArrayList (); private Map (); 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; }
使用同步块,您可以拥有多个同步器,以便可以同时进行多个同时但不冲突的事物.
可以使用反射API检查同步方法.这对于测试某些合同很有用,例如模型中的所有方法都是同步的.
以下代码段打印Hashtable的所有同步方法:
for (Method m : Hashtable.class.getMethods()) { if (Modifier.isSynchronized(m.getModifiers())) { System.out.println(m); } }
使用synchronized块的重要注意事项:注意您用作锁定对象的内容!
上面的user2277816的代码片段说明了这一点,因为对字符串文字的引用被用作锁定对象.意识到字符串文字在Java中自动实现,您应该开始看到问题:在文字"锁定"上同步的每一段代码共享同一个锁!这很容易导致完全不相关的代码片段死锁.
您需要注意的不仅仅是String对象.盒装基元也是一种危险,因为自动装箱和valueOf方法可以重复使用相同的对象,具体取决于值.
有关详细信息,请参阅:https: //www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused
经常在方法级别上使用锁定太粗鲁了.为什么通过锁定整个方法来锁定一段不访问任何共享资源的代码.由于每个对象都有一个锁,因此您可以创建虚拟对象来实现块级同步. 块级别更有效,因为它不会锁定整个方法.
这里有一些例子
方法级别
class MethodLevel { //shared among threads SharedResource x, y ; public void synchronized method1() { //multiple threads can't access } public void synchronized method2() { //multiple threads can't access } public void method3() { //not synchronized //multiple threads can access } }
块级别
class BlockLevel { //shared among threads SharedResource x, y ; //dummy objects for locking Object xLock = new Object(); Object yLock = new Object(); public void method1() { synchronized(xLock){ //access x here. thread safe } //do something here but don't use SharedResource x, y // because will not be thread-safe synchronized(xLock) { synchronized(yLock) { //access x,y here. thread safe } } //do something here but don't use SharedResource x, y //because will not be thread-safe }//end of method1 }
[编辑]
对于Collection
like Vector
和Hashtable
它们是同步的时候ArrayList
或者HashMap
你需要设置synchronized关键字或调用Collections同步方法:
Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map List myList = Collections.synchronizedList (myList); // single lock for the entire list
唯一的区别是:synchronized块允许粒度锁定,与同步方法不同
基本上,synchronized
通过避免内存不一致错误,已经使用块或方法来编写线程安全代码.
这个问题非常陈旧,在过去的7年里,很多事情都发生了变化.为线程安全引入了新的编程结构.
您可以使用高级并发API而不是synchronied
块来实现线程安全.本文档页面提供了良好的编程结构,以实现线程安全.
锁定对象支持锁定习惯用法,简化了许多并发应用程序.
执行程序定义用于启动和管理线程的高级API.java.util.concurrent提供的执行程序实现提供适用于大规模应用程序的线程池管理.
并发集合使管理大量数据更容易,并且可以大大减少同步需求.
Atomic Variables具有最小化同步并有助于避免内存一致性错误的功能.
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包.
请参阅此相关问题:
同步与锁定