使用同步方法而不是同步块是否有优势?
有没有人可以告诉我同步方法优于同步块的例子?
有没有人可以告诉我同步方法优于同步块的例子?谢谢。
使用同步方法的块没有明显的优势。
也许唯一的一个(但我不会称之为优势)是你不需要包含这个对象引用。
方法:
public synchronized void method() { // blocks "this" from here.... ... ... ... } // to here
块:
public void method() { synchronized( this ) { // blocks "this" from here .... .... .... .... } // to here... }
看到? 根本没有优势。
块虽然比方法有优势,但主要是灵活性,因为可以使用另一个对象作为锁,而同步方法将locking整个对象。
比较:
// 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() { synchronize(inputLock) { ... } } private void someOutputRelatedWork() { synchronize(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 }
唯一真正的区别是一个同步块可以select它同步的对象。 一个synchronized方法只能使用'this'
(或者相应的Class实例用于一个同步的类方法)。 例如,这些在语义上是等同的:
synchronized void foo() { ... } void foo() { synchronized (this) { ... } }
后者更灵活,因为它可以竞争任何对象的关联锁,通常是成员variables。 它也更精细,因为您可以在块之前和之后执行并发代码,但仍然在方法中。 当然,通过将并发代码重构为单独的非同步方法,您可以轻松地使用同步方法。 使用任何一个让代码更易理解的地方。
同步方法
优点:
- 您的IDE可以指示同步的方法。
- 语法更紧凑。
- 强制将同步块拆分为单独的方法。
缺点:
- 同步到这个,所以也可以让外人同步到它。
- 将代码移动到同步块之外更加困难。
同步块
优点:
- 允许对锁使用私有variables,从而强制锁保留在类中。
- 通过search对variables的引用可以find同步块。
缺点:
- 语法比较复杂,所以代码难以阅读。
就个人而言,我更喜欢使用同步方法的类只针对需要同步的事物。 这样的类应该尽可能小,因此应该很容易查看同步。 其他人不需要关心同步。
主要区别在于,如果使用同步块,则可以locking除此以外的对象,从而使其更加灵活。
假设你有一个消息队列和多个消息生产者和消费者。 我们不希望生产者互相干扰,但消费者应该能够检索消息,而不必等待生产者。 所以我们只是创build一个对象
Object writeLock = new Object();
从现在起,每当生产者想要添加一条新消息时,我们只需要locking它:
synchronized(writeLock){ // do something }
所以消费者仍然可以阅读,生产者将被locking。
同步的方法
同步方法有两个作用。
首先,当一个线程正在执行一个对象的同步方法时,所有其他调用同一对象的同步方法的线程将阻塞(挂起执行),直到第一个线程完成对象。
其次,当一个同步的方法退出时,它会自动build立一个与先前同步对象的任何后续调用同步方法的before-before关系。 这保证了对所有线程都可见的对象状态的改变。
请注意,构造函数不能同步 – 在构造函数中使用synchronized关键字是一个语法错误。 同步构造函数没有意义,因为只有创build对象的线程在构build时才能访问它。
同步语句
与synchronized方法不同,synchronized语句必须指定提供内部锁的对象:大多数情况下,我使用它来同步对列表或映射的访问,但是我不想阻止访问对象的所有方法。
问:内部锁和同步同步是围绕被称为内部锁或监视器锁的内部实体构build的。 (API规范通常将此实体简称为“监视器”。)在同步的两个方面(强制对对象状态的独占访问和build立发生)之前,内部锁都起着重要作用。
每个对象都有一个与之相关的固有锁。 按照惯例,需要独占且一致地访问对象字段的线程在访问对象之前必须先获取对象的内部锁,然后在完成内部锁时释放内部锁。 一个线程被认为拥有获取锁和释放锁之间的固有锁。 只要一个线程拥有一个内部锁,其他线程就不能获得同一个锁。 另一个线程在尝试获取锁时会阻塞。
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执行一个同步的方法时,正在执行的线程标识该方法的method_info结构已经设置了ACC_SYNCHRONIZED标志,然后它自动获取该对象的锁,调用该方法并释放该锁。 如果发生exception,线程将自动释放该锁。
另一方面,同步方法块绕过了JVM的内置支持,用于获取对象的locking和exception处理,并要求以字节代码明确写入该function。 如果读取带有同步块的方法的字节码,则会看到十多个额外的操作来pipe理此function。
这显示调用生成一个同步的方法和一个同步块:
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
同步方法和块之间的一个显着区别是,同步块通常会减less锁的范围。 由于锁的范围与性能成反比,所以总是最好只locking代码的关键部分。 使用synchronized块的最好例子之一是在Singleton模式中进行双重检查locking ,而不是locking整个getInstance()
方法,我们只locking用于创buildSingleton实例的关键代码段。 这大大提高了性能,因为只需要一次或两次locking。
在使用同步的方法时,如果同时使用静态同步方法和非静态同步方法,则需要格外小心。
大多数情况下,我使用它来同步访问列表或地图,但我不想阻止访问对象的所有方法。
在下面的代码中,修改列表的一个线程不会阻塞等待修改映射的线程。 如果方法在对象上同步,则每个方法即使正在进行的修改不会发生冲突,也必须等待。
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; }
通过同步块,可以有多个同步器,以便同时进行多个同时但不冲突的事情。
可以使用reflectionAPI检查同步的方法。 这对于testing某些合同是有用的,例如模型中的所有方法都是同步的 。
以下片段打印所有Hashtable的同步方法:
for (Method m : Hashtable.class.getMethods()) { if (Modifier.isSynchronized(m.getModifiers())) { System.out.println(m); } }
有关使用同步块的重要说明:小心你用作锁对象!
上面的user2277816的代码片断说明了这一点,即对string文字的引用被用作locking对象。 意识到string文字会被自动插入到Java中,您应该开始看到这个问题:每个在文字“locking”上同步的代码都共享同一个锁! 这可能很容易导致与完全不相关的代码段的死锁。
这不只是string对象,你需要小心。 装箱原语也是一个危险,因为自动装箱和valueOf方法可以重用相同的对象,具体取决于值。
有关更多信息,请参阅: https : //www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+rereused
通常在方法级别上使用locking太粗鲁了。 为什么要locking一段不能访问任何共享资源的代码,通过locking整个方法。 由于每个对象都有一个锁,所以可以创build虚拟对象来实现块级同步。 块级别更高效,因为它不locking整个方法。
这里举一些例子
方法级别
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 }
[编辑]
对于像Vector
和Hashtable
这样的Collection
,它们在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
同步方法用于locking所有对象同步块用于locking特定对象
一般来说,除了明确使用对象的监视器和隐式的这个对象之外,这些通常是相同的。 我认为有时会忽略同步方法的一个缺点,即在使用“this”引用进行同步时,您将留下外部对象locking在相同对象上的可能性。 如果碰到它,这可能是一个非常微妙的错误。 在内部显式对象或其他现有字段上同步可以避免这个问题,完全封装同步。
正如已经说过的那样,同步块可以使用用户定义的variables作为锁对象,当同步函数只使用“this”的时候。 当然,你可以使用应该同步的函数区域进行操作。 但是大家都说使用“this”作为锁对象的同步函数和覆盖整个函数的block没有区别。 这是不正确的,区别在于两种情况下都会产生的字节码。 在同步块使用的情况下,应该分配引用“this”的局部variables。 因此,我们将有一个更大的function大小(不相关,如果你只有less数function)。
更详细的解释你可以在这里find差异: http : //www.artima.com/insidejvm/ed2/threadsynchP.html
我知道这是一个古老的问题,但是通过我在这里快速阅读答复,我没有看到任何人提到有时synchronized
方法可能是错误的locking。
Java实践中的并发(第72页):
public class ListHelper<E> { public List<E> list = Collections.syncrhonizedList(new ArrayList<>()); ... public syncrhonized boolean putIfAbsent(E x) { boolean absent = !list.contains(x); if(absent) { list.add(x); } return absent; }
上面的代码具有线程安全的外观 。 但是,事实上并非如此。 在这种情况下,锁在类的实例上获得。 但是, 列表可能会被另一个不使用该方法的线程修改。 正确的做法是使用
public boolean putIfAbsent(E x) { synchronized(list) { boolean absent = !list.contains(x); if(absent) { list.add(x); } return absent; } }
上面的代码将阻止所有试图修改列表的 线程修改列表 ,直到同步块完成。
唯一的区别是: 同步块允许粒度locking,与同步方法不同
基本上synchronized
块或方法已经被用来通过避免内存不一致错误来编写线程安全代码。
这个问题很古老,在过去的七年里,很多事情已经改变了。 已经为线程安全引入了新的编程结构。
您可以通过使用高级并发API而不是synchronied
块来实现线程安全。 这个文档页面提供了很好的编程结构来实现线程安全。
锁对象支持简化许多并发应用程序的locking习惯用法。
执行者定义了一个用于启动和pipe理线程的高级API。 由java.util.concurrent提供的执行器实现提供了适用于大规模应用程序的线程池pipe理。
并发集合可以更容易地pipe理大量的数据集合,并且可以大大减less同步的需要。
primefacesvariables具有最小化同步的function,有助于避免内存一致性错误。
ThreadLocalRandom (在JDK 7中)提供了从多个线程高效地生成伪随机数。
更好地replace同步是ReentrantLock ,它使用Lock
API
可重入的互斥锁具有与使用同步方法和语句访问的隐式监视器锁相同的基本行为和语义,但具有扩展function。
locking示例:
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包。
也请参考这个相关的问题:
同步与locking
从Java规范总结: http : //www.cs.cornell.edu/andru/javaspec/17.doc.html
synchronized语句(§14.17)计算对象的引用; 它会尝试对该对象执行locking操作,直到locking操作成功完成,才会继续进行。 …
一个同步方法(§8.4.3.5)在被调用时自动执行一个locking动作; 在locking动作成功完成之前,其主体不会被执行。 如果方法是一个实例方法 ,它将locking与调用它的实例相关联的锁(也就是说,在执行方法主体期间将被称为此的对象)。 如果方法是静态的 ,它将locking与表示方法定义的类的Class对象关联的锁。 …
基于这些描述,我会说大多数以前的答案是正确的,同步的方法可能是特别有用的静态方法,否则你将不得不弄清楚如何获得“类对象,代表的方法定义“。
编辑:我原本以为这些是实际的Java规范的引号。 澄清这个页面只是对规格的总结/解释
实际上,同步方法优于同步块的优点在于它们更加抗拒白痴, 因为你不能select一个任意locking的对象,所以你不能误用synchronized方法的语法来做一些愚蠢的事情,比如lockingstring或者locking从线程下面改变的可变字段的内容。
另一方面,使用同步方法,您无法保护任何可以获取对象引用的线程获取的locking。
所以在方法上使用synchronized作为修饰符可以更好地保护你的牛不受伤害,同时使用synchronized块和私有的最终锁对象更好地保护你自己的代码。
在同步方法的情况下,将在对象上获取locking。 但是,如果您使用同步块进行操作,则可以select指定要获取锁的对象。
例如:
Class Example { String test = "abc"; // lock will be acquired on String test object. synchronized (test) { // do something } lock will be acquired on Example Object public synchronized void testMethod() { // do some thing } }
TLDR; 既不使用synchronized
修饰符,也不使用synchronized(this){...}
expression式,但synchronized(myLock){...}
,其中myLock
是持有私有对象的最终实例字段。
在方法声明中使用synchronized
修饰符和方法体中的synchronized(..){ }
expression式之间的区别是:
- 方法签名上指定的
synchronized
修饰符- 在生成的JavaDoc中是可见的,
- 在testing方法修饰符为Modifier.SYNCHRONIZED时 ,通过reflection以编程方式确定,
- 与
synchronized(this) { .... }
相比,需要更less的键入和缩进 - (取决于您的IDE)在课程大纲和代码完成中可见,
- 在非静态方法上声明时使用
this
对象作为锁,或者在静态方法中声明时使用封闭类。
-
synchronized(...){...}
expression式允许您- 只能同步方法体部分的执行,
- 要在构造函数或( 静态 )初始化块中使用,
- select控制同步访问的locking对象。
但是,使用synchronized
修饰符或synchronized(...) {...}
作为锁对象(如synchronized(this) {...}
),具有相同的缺点。 两者都使用它自己的实例作为locking对象进行同步。 这是非常危险的,因为不仅对象本身,而且任何其他外部对象/代码都可以将其用作具有潜在严重副作用(性能下降和死锁 )的同步锁。
因此,最佳做法是既不使用synchronized
修饰符,也不使用synchronized(...)
expression式与this
锁对象结合,而是使用this
对象专用的锁对象。 例如:
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 } } }
与线程同步。 1)从不使用同步(this)它不工作的线程。 与(this)同步使用当前线程作为locking线程对象。 由于每个线程独立于其他线程,所以没有协调同步。 2)代码testing表明,在Mac 1.6上的Java 1.6中,方法同步不起作用。 3)synchronized(lockObj)其中lockObj是所有线程同步的公共共享对象。 4)ReenterantLock.lock()和.unlock()工作。 请参阅Java教程。
以下代码显示了这些点。 它还包含线程安全的Vector,它将替代ArrayList,以显示添加到Vector的许multithreading不会丢失任何信息,而与ArrayList相同的则会丢失信息。 0)当前代码显示由于竞态条件造成的信息丢失A)注释当前标注的A行,取消注释上面的A行,然后运行,方法丢失数据,但不应该。 B)反向步骤A,取消注释B和//结束块}。 然后运行查看结果不丢失数据C)注释掉B,取消注释C.运行,看到同步(this)丢失数据,如预期的那样。 没有时间来完成所有的变化,希望这有助于。 如果(this)上的同步或方法同步起作用,请说明您testing的Java和OS的版本。 谢谢。
import java.util.*; /** RaceCondition - Shows that when multiple threads compete for resources thread one may grab the resource expecting to update a particular area but is removed from the CPU before finishing. Thread one still points to that resource. Then thread two grabs that resource and completes the update. Then thread one gets to complete the update, which over writes thread two's work. DEMO: 1) Run as is - see missing counts from race condition, Run severa times, values change 2) Uncomment "synchronized(countLock){ }" - see counts work Synchronized creates a lock on that block of code, no other threads can execute code within a block that another thread has a lock. 3) Comment ArrayList, unComment Vector - See no loss in collection Vectors work like ArrayList, but Vectors are "Thread Safe" May use this code as long as attribution to the author remains intact. /mf */ public class RaceCondition { private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#) // private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#) private String countLock="lock"; // Object use for locking the raceCount private int raceCount = 0; // simple add 1 to this counter private int MAX = 10000; // Do this 10,000 times private int NUM_THREADS = 100; // Create 100 threads public static void main(String [] args) { new RaceCondition(); } public RaceCondition() { ArrayList<Thread> arT = new ArrayList<Thread>(); // Create thread objects, add them to an array list for( int i=0; i<NUM_THREADS; i++){ Thread rt = new RaceThread( ); // i ); arT.add( rt ); } // Start all object at once. for( Thread rt : arT ){ rt.start(); } // Wait for all threads to finish before we can print totals created by threads for( int i=0; i<NUM_THREADS; i++){ try { arT.get(i).join(); } catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); } } // All threads finished, print the summary information. // (Try to print this informaiton without the join loop above) System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n", MAX*NUM_THREADS, raceList.size(), raceCount ); System.out.printf("Array lost %,d. Count lost %,d\n", MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount ); } // end RaceCondition constructor class RaceThread extends Thread { public void run() { for ( int i=0; i<MAX; i++){ try { update( i ); } // These catches show when one thread steps on another's values catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); } catch( OutOfMemoryError oome ) { System.out.print("O"); } } } // so we don't lose counts, need to synchronize on some object, not primitive // Created "countLock" to show how this can work. // Comment out the synchronized and ending {, see that we lose counts. // public synchronized void update(int i){ // use A public void update(int i){ // remove this when adding A // synchronized(countLock){ // or B // synchronized(this){ // or C raceCount = raceCount + 1; raceList.add( i ); // use Vector // } // end block for B or C } // end update } // end RaceThread inner class } // end RaceCondition outter class