【问题标题】:Is there an advantage to use a Synchronized Method instead of a Synchronized Block?使用同步方法而不是同步块有优势吗?
【发布时间】:2010-10-09 02:57:12
【问题描述】:

谁能通过例子告诉我同步方法相对于同步块的优势?

【问题讨论】:

标签: java multithreading concurrency locking synchronized


【解决方案1】:

使用同步块,您可以拥有多个同步器,以便多个同时但不冲突的事情可以同时进行。

【讨论】:

    【解决方案2】:

    唯一真正的区别是同步块可以选择在哪个对象上同步。同步方法只能使用'this'(或同步类方法对应的类实例)。例如,这些在语义上是等价的:

    synchronized void foo() {
      ...
    }
    
    void foo() {
        synchronized (this) {
          ...
        }
    }
    

    后者更灵活,因为它可以竞争任何对象的关联锁,通常是一个成员变量。它也更加细化,因为您可以在块之前和之后执行并发代码,但仍在方法内。当然,您可以通过将并发代码重构为单独的非同步方法来轻松使用同步方法。使用使代码更易于理解的那个。

    【讨论】:

    • 如果 foo() 中的所有代码不需要同步,后者也有好处。
    • 这是真的,但不是“勇士”问的:“同步方法的优势”没有。
    【解决方案3】:

    注意:静态同步方法和块作用于 Class 对象。

    public class MyClass {
       // locks MyClass.class
       public static synchronized void foo() {
    // do something
       }
    
       // similar
       public static void foo() {
          synchronized(MyClass.class) {
    // do something
          }
       }
    }
    

    【讨论】:

      【解决方案4】:

      主要区别在于,如果您使用同步块,您可以锁定除 this 以外的对象,这样可以更加灵活。

      假设您有一个消息队列和多个消息生产者和消费者。我们不希望生产者相互干扰,但消费者应该能够检索消息而无需等待生产者。 所以我们只是创建一个对象

      Object writeLock = new Object();
      

      从现在开始,每当生产者想要添加新消息时,我们都会锁定它:

      synchronized(writeLock){
        // do something
      }
      

      因此消费者仍然可以阅读,而生产者将被锁定。

      【讨论】:

      • 您的示例仅限于非破坏性读取。如果读取从队列中删除消息,如果它在生产者写入队列的某个时间完成,则会失败。
      【解决方案5】:

      谁能通过示例告诉我同步方法相对于同步块的优势?谢谢。

      与块相比,使用同步方法没有明显的优势。

      也许唯一(但我不会称之为优势)是您不需要包含对象引用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
      }
      

      【讨论】:

      • 对 API 使用者的一个好处是,在方法声明中使用 synchronized 关键字还明确声明该方法在对象实例上同步并且(可能)是线程安全的。
      • 我知道这是一个老问题,但在某些圈子中同步“this”被认为是一种反模式。意想不到的后果是,在类之外,有人可以锁定等于“this”的对象引用,并阻止其他线程通过类内的障碍,从而可能造成死锁情况。创建“私有最终对象 = new Object();”纯粹用于锁定目的的变量是常用的解决方案。 Here's another question 与此问题直接相关。
      • “而同步方法会锁定整个类。”这是不正确的。它不会锁定完整的类,而是锁定完整的实例。来自同一类的多个对象拥有自己的锁。 :) 问候
      • 有趣的是,使用同步方法会导致生成的字节码少 1 条指令,因为方法的签名中包含同步位。由于字节码的长度是方法是否内联的一个因素,因此将块移动到方法签名可能是决策的差异。反正理论上。我不会根据保存的单个字节码指令做出设计决定,这似乎是一个糟糕的主意。但仍然,它的区别。 =)
      • @corsiKa:你保存了不止一条指令。 synchronized 块使用两条指令实现,monitorentermonitorexit加上一个异常处理程序,确保即使在异常情况下也调用 monitorexit。使用synchronized 方法时,这些都保存了。
      【解决方案6】:

      我经常使用它来同步对列表或地图的访问,但我不想阻止对对象的所有方法的访问。

      在以下代码中,修改列表的线程不会阻塞等待正在修改映射的线程。如果方法在对象上同步,那么每个方法都必须等待,即使它们所做的修改不会冲突。

      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;
      }
      

      【讨论】:

        【解决方案7】:

        同步方法

        优点:

        • 您的 IDE 可以指示同步方法。
        • 语法更紧凑。
        • 强制将同步块拆分为单独的方法。

        缺点:

        • 与此同步,因此外部人员也可以与之同步。
        • 很难将代码移出同步块。

        同步块

        优点:

        • 允许为锁使用私有变量,从而强制锁留在类中。
        • 可以通过搜索对变量的引用找到同步块。

        缺点:

        • 语法更复杂,因此代码更难阅读。

        就我个人而言,我更喜欢使用只关注需要同步的类的同步方法。这样的类应该尽可能小,因此应该很容易查看同步。其他人不需要关心同步。

        【讨论】:

        • 当你说“留在课堂内”时,你的意思是“留在 object 内”,还是我错过了什么?
        【解决方案8】:

        一般来说,除了明确说明正在使用的对象的监视器与隐含的 this 对象之外,这些几乎相同。我认为有时会被忽略的同步方法的一个缺点是,在使用“this”引用进行同步时,外部对象可能会锁定在同一对象上。如果您遇到它,这可能是一个非常微妙的错误。在内部显式 Object 或其他现有字段上进行同步可以避免此问题,完全封装同步。

        【讨论】:

          【解决方案9】:

          同步方法用于锁定所有对象 同步块用于锁定特定对象

          【讨论】:

            【解决方案10】:

            同步方式

            同步方法有两种效果。
            首先,当一个线程正在为一个对象执行同步方法时,所有其他为同一对象调用同步方法的线程都会阻塞(暂停执行),直到第一个线程处理完该对象。

            其次,当一个同步方法退出时,它会自动与任何后续对同一对象的同步方法调用建立起之前的关系。这保证了对象状态的更改对所有线程都是可见的。

            请注意,构造函数不能同步——在构造函数中使用 synchronized 关键字是一个语法错误。同步构造函数没有意义,因为只有创建对象的线程才能在构造对象时访问它。

            同步语句

            与同步方法不同,同步语句必须指定提供内在锁的对象:大多数情况下,我使用它来同步对列表或映射的访问,但我不想阻止对对象的所有方法的访问。

            Q:内在锁和同步 同步是围绕称为内在锁或监视器锁的内部实体构建的。 (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();
                }
            }
            

            用同步方法交叉检查不同的输出,阻塞和不同步。

            【讨论】:

            【解决方案11】:

            可以使用反射 API 检查同步方法。这对于测试一些合约很有用,例如模型中的所有方法都是同步的

            下面的sn -p打印Hashtable的所有同步方法:

            for (Method m : Hashtable.class.getMethods()) {
                    if (Modifier.isSynchronized(m.getModifiers())) {
                        System.out.println(m);
                    }
            }
            

            【讨论】:

              【解决方案12】:

              与线程同步。 1)永远不要在线程中使用 synchronized(this) 它不起作用。与 (this) 同步使用当前线程作为锁定线程对象。由于每个线程都独立于其他线程,因此没有同步协调。 2) 代码测试表明,在 Mac 上的 Java 1.6 中,方法同步不起作用。 3) synchronized(lockObj) 其中 lockObj 是所有线程在其上同步的公共共享对象将起作用。 4) ReenterantLock.lock() 和 .unlock() 工作。请参阅 Java 教程。

              以下代码显示了这些要点。它还包含将替换 ArrayList 的线程安全 Vector,以表明添加到 Vector 的许多线程不会丢失任何信息,而与 ArrayList 相同的线程可能会丢失信息。 0) 当前代码显示由于竞争条件而丢失信息 A)注释当前标记的 A 行,并取消注释其上方的 A 行,然后运行,方法会丢失数据,但它不应该。 B) 反转步骤 A,取消注释 B 和 // 结束块 }。然后运行看看结果没有数据丢失 C)注释掉B,取消注释C。运行,见同步(this)如预期的那样丢失数据。 没有时间完成所有的变化,希望这会有所帮助。 如果在 (this) 上进行同步,或者方法同步有效,请说明您测试的 Java 和操作系统版本。谢谢。

              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
              

              【讨论】:

              • 与'(this)'同步 does 工作,并且 not '使用当前线程作为同步object',除非当前对象属于扩展 Thread 的类。 -1
              【解决方案13】:

              正如这里已经说过的,同步块可以使用用户定义的变量作为锁对象,当同步函数只使用“this”时。当然,您可以使用应该同步的功能区域进行操作。 但是每个人都说同步函数和使用“this”作为锁定对象覆盖整个函数的块之间没有区别。这不是真的,区别在于两种情况下都会生成的字节码。在同步块使用的情况下,应分配局部变量,该变量包含对“this”的引用。因此,我们的函数大小会稍大一些(如果您只有少数函数,则无关紧要)。

              您可以在此处找到有关差异的更详细说明: http://www.artima.com/insidejvm/ed2/threadsynchP.html

              【讨论】:

                【解决方案14】:

                使用同步块的重要注意事项:小心你使用什么作为锁对象!

                上面来自 user2277816 的代码 sn-p 说明了这一点,因为对字符串文字的引用被用作锁定对象。 意识到字符串字面量在 Java 中会自动进行实习,您应该开始看到问题:在字面量“锁”上同步的每段代码都共享同一个锁!这很容易导致完全不相关的代码段出现死锁。

                您需要小心的不仅仅是 String 对象。装箱的原语也是一种危险,因为自动装箱和 valueOf 方法可以重用相同的对象,具体取决于值。

                有关详细信息,请参阅: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused

                【讨论】:

                  【解决方案15】:

                  通常在方法级别上使用锁太粗鲁了。为什么要通过锁定整个方法来锁定一段不访问任何共享资源的代码。由于每个对象都有一个锁,您可以创建虚拟对象来实现块级同步。 块级别更高效,因为它不会锁定整个方法。

                  这里有一些例子

                  方法级别

                  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,如VectorHashtable,当ArrayListHashMap 不同步时,它们是同步的,您需要设置同步关键字或调用集合同步方法:

                  Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
                  List myList = Collections.synchronizedList (myList); // single lock for the entire list
                  

                  【讨论】:

                    【解决方案16】:

                    来自 Java 规范摘要: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

                    同步语句(第 14.17 节)计算对对象的引用; 然后它会尝试对该对象执行锁定操作,但不会 继续进行,直到锁定操作成功完成。 ...

                    同步方法(第 8.4.3.5 节)自动执行锁定操作 当它被调用时;它的主体在锁定动作完成之前不会执行 成功完成。 如果方法是实例方法,它 锁定与调用它的实例关联的锁 (也就是说,在执行期间将被称为 this 的对象 方法的主体)。 如果方法是静态的,它会锁定 与代表类的 Class 对象关联的锁 定义了哪个方法。 ...

                    基于这些描述,我会说大多数以前的答案都是正确的,同步方法可能对静态方法特别有用,否则您将不得不弄清楚如何获取“表示其中的类的类对象”方法已定义。”

                    编辑:我最初认为这些是实际 Java 规范的引用。澄清此页面只是规范的摘要/说明

                    【讨论】:

                      【解决方案17】:

                      当 java 编译器将你的源代码转换为字节码时,它处理同步方法和同步块的方式非常不同。

                      当JVM执行同步方法时,执行线程识别到方法的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
                      

                      同步方法和块之间的一个显着区别是,同步块通常会缩小锁定范围。由于锁定范围与性能成反比,因此最好只锁定代码的关键部分。使用同步块的最佳示例之一是 double checked locking in Singleton pattern,我们只锁定用于创建 Singleton 实例的代码的关键部分,而不是锁定整个 getInstance() 方法。这大大提高了性能,因为锁定只需要一两次。

                      在使用同步方法时,如果同时使用静态同步和非静态同步方法,则需要格外小心。

                      【讨论】:

                      • 如果我们看字节码同步方法字节码更紧凑和简单,那么为什么它没有同步块更快?
                      • @eatSleepCode 请注意,这是由 JVM 进一步“编译”的字节码。 JVM会在运行代码之前添加必要的monitorentermonitorexit
                      【解决方案18】:

                      实际上,同步方法相对于同步块的优势在于它们更耐白痴;因为您不能选择要锁定的任意对象,所以不能滥用同步方法语法来执行愚蠢的事情,例如锁定字符串文字或锁定从线程下更改出来的可变字段的内容。

                      另一方面,使用同步方法,您无法保护锁不被任何可以获取对象引用的线程获取。

                      因此,在方法上使用 synchronized 作为修饰符可以更好地保护您的工作人员免受伤害,而将同步块与私有最终锁定对象结合使用可以更好地保护您自己的代码免受工作人员的伤害。

                      【讨论】:

                        【解决方案19】:

                        在同步方法的情况下,将在对象上获取锁。但是如果你使用同步块,你可以选择指定一个对象,在该对象上获取锁。

                        例子:

                            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
                           } 
                        
                           }
                        

                        【讨论】:

                          【解决方案20】:

                          唯一的区别:与同步方法不同,同步块允许粒度锁定

                          基本上synchronized 块或方法已用于通过避免内存不一致错误来编写线程安全代码。

                          这个问题很老了,在过去的 7 年里,很多事情都发生了变化。 为线程安全引入了新的编程结构。

                          您可以通过使用高级并发 API 而不是 synchronied 块来实现线程安全。该文档page 提供了良好的编程结构来实现线程安全。

                          Lock Objects 支持可简化许多并发应用程序的锁定习惯用法。

                          Executors 定义了一个用于启动和管理线程的高级 API。 java.util.concurrent 提供的 Executor 实现提供了适合大规模应用的线程池管理。

                          Concurrent Collections 可以更轻松地管理大量数据集合,并且可以大大减少同步需求。

                          Atomic Variables 具有最小化同步并有助于避免内存一致性错误的功能。

                          ThreadLocalRandom(在 JDK 7 中)提供从多个线程高效生成伪随机数。

                          同步的更好的替代品是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.concurrentjava.util.concurrent.atomic 包以了解其他编程结构。

                          也参考这个相关问题:

                          Synchronization vs Lock

                          【讨论】:

                            【解决方案21】:

                            我知道这是一个老问题,但是通过快速阅读这里的回复,我并没有真正看到有人提到 synchronized 方法有时可能是 错误 锁定。
                            来自 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;
                            }
                            

                            上面的代码具有线程安全的外观。然而,实际上并非如此。在这种情况下,锁定是在类的实例上获得的。但是,list 可能会被另一个不使用该方法的线程修改。正确的方法是使用

                            public boolean putIfAbsent(E x) {
                             synchronized(list) {
                              boolean absent = !list.contains(x);
                              if(absent) {
                                list.add(x);
                              }
                              return absent;
                            }
                            }
                            

                            上面的代码会阻止所有线程尝试修改list,直到同步块完成为止。

                            【讨论】:

                            • 目前正在阅读这本书...我想知道...如果该列表是私有的而不是公共的并且只有 putIfAbsent 方法,那么 synchronized(this) 就足够了,对吗?手头的问题是因为列表也可以在 ListHelper 之外修改?
                            • @dtc 是的,如果列表是私有的并且没有泄漏到类中的其他任何地方,那么这就足够了,只要您将类中修改列表的所有其他方法也标记为同步。但是,如果存在不一定需要同步的代码日志,则锁定整个方法而不仅仅是 List 可能会导致性能问题
                            • 这是有道理的。非常感谢您的回答! tbh,我发现这本书在扩展我的知识以及如何处理多线程方面非常有用,但它也给我带来了一个全新的混乱世界
                            【解决方案22】:

                            TLDR;既不使用synchronized 修饰符也不使用synchronized(this){...} 表达式,而是使用synchronized(myLock){...},其中myLock 是一个包含私有对象的最终实例字段。


                            在方法声明中使用synchronized修饰符和在方法体中使用synchronized(..){ }表达式的区别如下:

                            • 在方法签名上指定的synchronized 修饰符
                              1. 在生成的JavaDoc中可见,
                              2. 在为Modifier.SYNCHRONIZED 测试方法的修饰符时,可通过reflection 以编程方式确定,
                              3. synchronized(this) { .... } 相比,需要更少的输入和缩进,并且
                              4. (取决于您的 IDE)在课程大纲和代码完成中可见,
                              5. 在非静态方法上声明时使用this 对象作为锁,在静态方法上声明时使用封闭类。
                            • synchronized(...){...} 表达式允许您
                              1. 仅同步方法主体部分的执行,
                              2. 在构造函数或 (static) 初始化块中使用,
                              3. 选择控制同步访问的锁对象。

                            但是,使用synchronized 修饰符或synchronized(...) {...}this 作为锁定对象(如synchronized(this) {...})具有相同的缺点。两者都使用它自己的实例作为要同步的锁对象。这是危险的,因为不仅对象本身,而且 任何 持有对该对象的引用的其他外部对象/代码也可以将其用作具有潜在严重副作用的同步锁(性能下降和deadlocks )。

                            因此,最佳实践是既不使用synchronized 修饰符也不使用synchronized(...) 表达式与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
                                    }
                                }
                            }
                            

                            【讨论】:

                              【解决方案23】:

                              我想这个问题是关于 Thread Safe SingletonLazy initialization with Double check locking 之间的区别。当我需要实现一些特定的单例时,我总是参考这篇文章。

                              嗯,这是一个线程安全的单例

                              // Java program to create Thread Safe 
                              // Singleton class 
                              public class GFG  
                              { 
                                // private instance, so that it can be 
                                // accessed by only by getInstance() method 
                                private static GFG instance; 
                              
                                private GFG()  
                                { 
                                  // private constructor 
                                } 
                              
                               //synchronized method to control simultaneous access 
                                synchronized public static GFG getInstance()  
                                { 
                                  if (instance == null)  
                                  { 
                                    // if instance is null, initialize 
                                    instance = new GFG(); 
                                  } 
                                  return instance; 
                                } 
                              } 
                              

                              优点:

                              1. 可以进行延迟初始化。

                              2. 它是线程安全的。

                              缺点:

                              1. getInstance() 方法是同步的,因此会导致性能下降,因为多个线程无法同时访问它。

                              这是一个带有双重检查锁定的延迟初始化

                              // Java code to explain double check locking 
                              public class GFG  
                              { 
                                // private instance, so that it can be 
                                // accessed by only by getInstance() method 
                                private static GFG instance; 
                              
                                private GFG()  
                                { 
                                  // private constructor 
                                } 
                              
                                public static GFG getInstance() 
                                { 
                                  if (instance == null)  
                                  { 
                                    //synchronized block to remove overhead 
                                    synchronized (GFG.class) 
                                    { 
                                      if(instance==null) 
                                      { 
                                        // if instance is null, initialize 
                                        instance = new GFG(); 
                                      } 
                              
                                    } 
                                  } 
                                  return instance; 
                                } 
                              } 
                              

                              优点:

                              1. 可以进行延迟初始化。

                              2. 它也是线程安全的。

                              3. 克服了因同步关键字而降低的性能。

                              缺点:

                              1. 第一次,它会影响性能。

                              2. 作为缺点。双重检查锁定方法是可以忍受的,因此可以 用于高性能多线程应用程序。

                              更多详情请参考这篇文章:

                              https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2013-01-31
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                相关资源
                                最近更新 更多