使用同步方法而不是同步块是否有优势?


401

Answers:


431

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

在块上使用同步方法没有明显的优势。

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

44
对API使用者的好处是,在方法声明中使用synced关键字还显式声明了该方法在对象实例上进行同步,并且(大概)是线程安全的。
Scrubbie 2012年

59
我知道这是一个老问题,但是在某些圈子中同步“ this”被认为是一种反模式。意外的结果是,在类外部,某些人可以锁定等于“ this”的对象引用,并防止其他线程通过类内的障碍,从而可能导致死锁情况。创建一个“ private final Object = new Object();” 仅出于锁定目的的变量是经常使用的解决方案。 这是另一个与此问题直接相关的问题。
justin.hughey 2013年

30
“而同步该方法将锁定整个类。” 这是不正确的。它不会锁定完整的类,而是锁定完整的实例。同一类中的多个对象拥有自己的所有锁。:)
问候-codepleb

4
有趣的是,使用同步方法将使生成的字节码少1条指令,因为方法将同步位烘焙到其签名中。由于字节码的长度是方法是否内联的一个因素,因此将块移至方法签名可能是决策上的差异。从理论上讲。我不会基于保存的单个字节码指令的设计决策,这似乎是一个糟糕的主意。但是,它仍然一个区别。=)
corsiKa 2014年

2
@corsiKa:您保存了多个指令。一个synchronized块使用两个指令monitorenter以及一个异常处理程序来实现monitorexit,该异常处理程序确保即使在特殊情况下也可以调用该程序。使用一种方法时,这些都已保存。monitorexitsynchronized
Holger

139

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

synchronized void foo() {
  ...
}

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

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


如果不需要同步foo()中的所有代码,则后者也有好处。
Evan

1
这是事实,但“战士”问的不是:“同步方法的优点”没有。
OscarRyz

76

同步方式

优点:

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

缺点:

  • 与此同步,因此也使外部人员也可以与之同步。
  • 将代码移到同步块之外比较困难。

同步块

优点:

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

缺点:

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

我个人更喜欢将同步方法与仅用于需要同步的对象一起使用的类。该类应尽可能小,因此应易于检查同步。其他人则不需要关心同步。


当您说“留在班级内”时,您是说“留在对象内 ”,还是我错过了什么?
OldPeculier 2014年

36

主要的区别是,如果使用一个同步块可能会比其他的物体上锁定这个这允许更加灵活。

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

Object writeLock = new Object();

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

synchronized(writeLock){
  // do something
}

因此,消费者可能仍会阅读,并且生产者将被锁定。


2
您的示例仅限于非破坏性读取。如果读取将消息从队列中删除,则在生产者写入队列的某个时间完成消息后,它将失败。
2013年

30

同步方式

同步方法有两个作用。
首先,当一个线程正在为一个对象执行同步方法时,所有其他为同一对象块调用同步方法的线程(挂起执行),直到第一个线程对该对象完成。

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

请注意,构造函数无法同步-在构造函数中使用synced关键字是语法错误。同步构造函数没有任何意义,因为只有创建对象的线程才可以在构造对象时对其进行访问。

同步语句

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

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

使用同步方法,块和不同步来交叉检查不同的输出。


10
+1是迄今为止唯一提及构造器无法同步的 +1 。也就是说,在构造函数中,您实际上只有一个选项:同步块。
ef2011 2012年

我按照指示测试了您的代码,但C始终为0,然后是-2024260031,这是唯一将其更改为哈希码的方法。应该看到什么行为?
贾斯汀·约翰逊


29

注意:静态同步方法和块可在Class对象上工作。

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

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

18

当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

同步方法和块之间的一个重要区别是,同步块通常会减小锁定范围。由于锁定范围与性能成反比,因此仅锁定关键代码段总是更好。使用同步块的最佳示例之一是在Singleton模式中进行双重检查锁定,在该模式中,不是锁定整个getInstance()方法,而是仅锁定用于创建Singleton实例的代码的关键部分。由于仅需要锁定一到两次,因此可以大大提高性能。

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


1
如果我们看一下字节码同步方法,字节码更紧凑,更简单,那么为什么它的同步块不能更快?
eatSleepCode 2015年

@eatSleepCode注意,这是字节码,JVM会进一步对其进行“编译”。JVM将增加在必要monitorentermonitorexit运行代码之前。
菲利普·库林

12

大多数情况下,我使用它来同步对列表或映射的访问,但我不想阻止对对象所有方法的访问。

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

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

使用同步块,您可以具有多个同步器,以便可以同时进行多个同时发生但无冲突的操作。


6

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

以下代码段显示了Hashtable的所有同步方法:

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

5

关于使用同步块的重要说明:小心用作锁定对象!

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

您不仅需要小心使用String对象。装箱的基元也是一种危险,因为自动装箱和valueOf方法可以根据值重用相同的对象。

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


5

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

这里有一些例子

方法级别

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不,你需要设定synchronized关键字或调用集合同步方法:

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

5

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

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

这个问题很老,在过去的7年中许多事情已经改变。为了线程安全,引入了新的编程结构。

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

锁定对象支持简化许多并发应用程序的成语。

执行程序定义了用于启动和管理线程的高级API。java.util.concurrent提供的执行程序实现提供适用于大规模应用程序的线程池管理。

并发收集使管理大型数据收集更加容易,并且可以大大减少同步需求。

原子变量具有最大程度地减少同步并有助于避免内存一致性错误的功能。

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

ReentrantLock是同步的更好替代品,它使用LockAPI

具有与使用同步方法和语句访问的隐式监视器锁相同的基本行为和语义的可重入互斥锁,但具有扩展功能。

带锁的示例:

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包。

也请参考以下相关问题:

同步与锁定


4

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


3

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


2

如前所述,当同步功能仅使用“ this”时,同步块可以将用户定义的变量用作锁定对象。当然,您可以对应该同步的功能区域进行操作。但是每个人都说,同步函数和使用“ this”作为锁定对象的块涵盖了整个函数之间没有区别。这是不正确的,在两种情况下都会生成字节码。在使用同步块的情况下,应分配局部变量,该变量保留对“ this”的引用。结果,我们的功能会更大一些(如果您只有少数几个功能,则不相关)。

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


2

对于同步方法,将在对象上获得锁定。但是,如果使用同步块,则可以选择指定要在其上获取锁的对象。

范例:

    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
   } 

   }

2

我知道这是一个老问题,但是通过快速阅读此处的回答,我并没有真正看到有人提到synchronized方法有时是错误的锁定。
从Java Concurrency in Practice(第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;
}
}

上面的代码将阻止所有试图修改列表的线程修改列表,直到同步块完成为止。


现在正在读这本书...我想知道...如果该列表是私有的而不是公共的,并且只有putIfAbsent方法,synced(this)就足够了吗?当前的问题是因为列表也可以在该ListHelper之外进行修改?
dtc

@dtc是的,如果列表是私有的,并且没有泄漏到类中的其他任何地方,那么只要您在该类中标记了其他所有将列表修改为同步的方法,就足够了。但是,List如果存在不一定需要同步的代码日志,则锁定整个方法而不是仅锁定方法可能会导致性能问题
aarbor

这就说得通了。非常感谢您的回答!tbh,我发现这本书在扩展我的知识以及如何处理多线程方面非常有用,但是它也给我带来了一个全新的困惑世界
dtc

2

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

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

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


1

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

同步语句(第14.17节)计算对对象的引用;然后,它尝试对该对象执行锁定操作,并且在锁定操作成功完成之前不会进一步进行操作。...

同步方法(第8.4.3.5节)在被调用时会自动执行锁定操作。在成功完成锁定操作之前,不会执行其主体。如果该方法是实例方法,则它将锁定与为其调用实例的实例(即,在执行方法主体期间将被称为此对象的对象)相关联的锁。如果该方法是static,则它将锁定与Class对象关联的锁,该Class对象代表定义该方法的类。...

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

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


1

TLDR;既不使用synchronized修饰符也不使用synchronized(this){...}表达式,但最终实例字段存放私有对象的synchronized(myLock){...}地方在哪里myLock


synchronized在方法声明中使用修饰符synchronized(..){ }与方法主体中的表达式之间的区别是:

  • synchronized方法签名上指定的修饰符
    1. 在生成的JavaDoc中可见
    2. 在测试方法的修改器的Modifier时,可以通过反射以编程方式确定.SYNCHRONIZED
    3. 与相比,需要更少的打字和缩进 synchronized(this) { .... }
    4. (取决于您的IDE)在类概述和代码完成中可见,
    5. this在非静态方法上声明时,将对象用作锁;在静态方法上声明时,将对象用作封闭类。
  • synchronized(...){...}表达式允许您
    1. 只同步方法主体各部分的执行,
    2. 被构造函数或(内使用静态)初始化块中,
    3. 选择控制同步访问的锁定对象。

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

因此,最佳实践是既不使用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
        }
    }
}

1

我想这个问题是关于线程安全单例带有双重检查锁定的惰性初始化之间的区别。当我需要实现一些特定的单例时,我总是参考本文。

好吧,这是一个线程安全单例

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


-3

与线程同步。1)永远不要在线程中使用synced(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。运行,请参见同步(此)丢失数据,这与预期的一样。没有时间完成所有的变化,希望对您有所帮助。如果与此同步,或者方法同步可行,请说明您测试的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

1
与同步“(这)” 工作,并且没有 '使用当前线程作为同步对象,除非当前的对象是可扩展条条的类。-1
洛恩侯爵,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.