“同步”真的只是语法糖吗?

我是multithreading新手,我编写了这段代码,通过同时运行线程增量并打印变量来打印数字1-10000。

这是我正在使用的代码:

package threadtest; public class Main{ static int i=0; static Object lock=new Object(); private static class Incrementer extends Thread{ @Override public void run(){ while (true){ synchronized(lock){ if (i>=10000) break; i++; System.out.println(i); } } } } public static void main(String[] args) { new Incrementer().start(); new Incrementer().start(); new Incrementer().start(); new Incrementer().start(); new Incrementer().start(); new Incrementer().start(); } } 

这工作 – 我编写了一个测试程序来检查输出,打印的数字按顺序正好是1-10000。

我的问题是:我听说synchronized只是语法糖。 但是,如果不使用它,我似乎无法取得成功。 我错过了什么?

synchronized绝不是任何语法糖。 如果不使用synchronized关键字,就无法在Java中使用锁。

在Java中锁定中存在“语法糖”的地方, synchronized可以应用于块(如上所述)和整个方法。 以下两种方法在语义上大致相同:

 synchronized void method1() { // ... do stuff ... } void method2() { synchronized(this) { // ... do stuff ... } } 

那你为什么要做第二个版本而不是第一个呢?

  • 同步方法调用比普通的旧方法调用慢得多,比如大约一个数量级。 如果不保证您的同步代码始终执行(比如说它是条件的),那么您可能不希望同步整个方法。
  • 同步方法持有锁的时间比同步块长(因为所有方法设置/拆除代码)。 上面的第二种方法将锁定时间更短,因为设置和拆除堆栈帧所花费的时间不会被锁定。
  • 如果使用同步块,您可以更精确地控制您正在锁定的内容。
  • (由starblue提供 )同步块可以使用除此之外的对象进行锁定,从而为您提供更灵活的锁定语义。

听起来你的消息来源是错的。 在编写线程安全代码时, syncrhonized关键字非常重要 – 并且可以正确使用。 听起来你自己的实validation明了这一点。

有关Java同步的更多信息:

Java同步方法

Java锁和同步语句

实际上,从Java 5开始,你(正式)在java.util.concurrent中有一套替代工具。 有关详细信息,请参见此处 正如文章中详述的那样,Java语言级别提供的监视器锁定模型具有许多重要的局限性,并且当存在一组复杂的相互依赖的对象和锁定关系使得实时锁定成为可能时,可能难以推断。 java.util.concurrent库提供了锁定语义,对于那些在POSIX类系统中有过经验的程序员来说可能更为熟悉

当然,“同步”只是句法糖 – 极端有用的句法糖。

如果你想要无糖的java程序,你应该直接用java字节代码编写VM规范中引用的monitorentermonitorexitlockunlock操作8.13 Locks and Synchronization

每个对象都有一个锁。 Java编程语言不提供执行单独锁定和解锁操作的方法; 相反,它们由高级构造隐式执行,这些构造总是安排正确配对这些操作。 (但是,Java虚拟机提供了单独的monitorenter和monitorexit指令,用于实现锁定和解锁操作。)

synchronized语句计算对象的引用; 然后它尝试对该对象执行锁定操作,并且在锁定操作成功完成之前不会继续进行。 (锁定操作可能会延迟,因为关于锁定的规则可能会阻止主存储器参与,直到某个其他线程准备好执行一个或多个解锁操作。)执行锁定操作后,将执行synchronized语句的主体。 通常,Java编程语言的编译器确保在执行synchronized语句主体之前执行的monitorenter指令实现的锁定操作与每当synchronized语句完成时由monitorexit指令实现的解锁操作匹配,无论是否完成是正常的还是突然的

同步方法在调用时自动执行锁定操作; 在锁定操作成功完成之前,它的主体不会执行。 如果该方法是实例方法,则它锁定与调用它的实例关联的锁(即,在执行方法体时将被称为this的对象)。 如果方法是静态的,它将锁定与Class对象关联的锁,该Class对象表示定义方法的类。 如果方法的主体的执行正常或突然完成,则在同一锁上自动执行解锁操作。

最佳实践是,如果变量由一个线程分配并由另一个线程使用或分配,则对该变量的所有访问都应包含在同步方法或同步语句中。

虽然Java编程语言的编译器通常保证锁的结构化使用(请参见第7.14节“同步”),但无法保证提交给Java虚拟机的所有代码都将遵循此属性。 允许实现Java虚拟机,但不要求强制执行以下两个保证结构化锁定的规则。

设T为线程,L为锁。 然后:

  1. 在方法调用期间由T在L上执行的锁定操作的数量必须等于在方法调用期间由T执行的解锁操作的数量,无论方法调用是正常还是突然完成。

  2. 在方法调用期间,在方法调用期间,由于方法调用,由T执行的解锁操作的数量可能超过自方法调用以来由T在L上执行的锁定操作的数量。

在不太正式的术语中,在方法调用期间,L上的每个解锁操作必须与L上的某些先前锁定操作匹配。

请注意,在调用调用方法时,Java虚拟机在调用synchronized方法时自动执行的锁定和解锁被认为是在调用方法的调用期间发生的。

在multithreading环境中编程时,同步是最重要的概念之一。 在使用同步时,必须考虑发生同步的对象。 例如,如果要同步静态方法,则同步必须在类级别上使用

 synchronized(MyClass.class){ //code to be executed in the static context } 

如果要实例化方法中的块,则同步必须使用在所有线程之间共享的对象的实例。 大多数人在第二点出错,因为它出现在你的代码中,同步似乎在不同的对象而不是单个对象上。