【问题标题】:Is 'synchronized' really just syntactic sugar?“同步”真的只是语法糖吗?
【发布时间】:2011-03-08 16:03:45
【问题描述】:

我是多线程的新手,我编写了这段代码,它通过同时运行的线程递增并打印一个变量来打印数字 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 只是语法糖。但是如果不使用它,我似乎无法取得成功的结果。我错过了什么?

【问题讨论】:

  • 当然不是。你能引用你听说过的消息来源吗?
  • @BalusC:查看已接受的答案。我认为消息来源只是将同步的 methods 称为语法糖——我不确定这是否完全正确,但至少它并不像我认为的那样与我的发现相矛盾。 @skaffman:据我所知,如果没有synchronized,我可能做错了:)
  • @incrediman synchronized 在方法上与将方法主体放入 synchronized (this) { ... } (或静态方法 synchronized (MyClass.class) { ... })或多或少相同。 (它在类文件中的表示方式确实不同,但这通常不应该让您担心。)
  • 顺便说一句:由于您可能已启用 Biased Locked,(这是默认设置)一个线程将获取锁,而其他线程将进入睡眠状态。即很可能所有工作都将由一个线程完成,其余线程将等待它完成。这不是错误,在您的情况下,它是执行这些线程的最有效方式。
  • @Peter:这项工作并不是由一个线程完成的。在上面代码的一个变体中,我给每个线程一个名字,并让他们输出他们的名字和数字——工作是平等的。知道为什么会这样吗?

标签: java multithreading synchronized


【解决方案1】:

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

在 Java 中的锁中有一种“语法糖”,synchronized 既可以应用于块(正如您在上面所做的那样)也可以应用于整个方法。以下两种方法在语义上大致等价:

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

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

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

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

【讨论】:

  • 有了块,你可以使用一些私有对象作为锁,这样可以防止外部代码的干扰。
  • 从 java 1.5 开始,您可以使用 java.util.concurrent.Lock 来保护对关键区域的访问。这实际上比在 1.5 和 1.6 中使用同步更快。所以你的开场白是错误的。
【解决方案2】:

听起来您的消息来源是错误的。在编写线程安全代码时,syncrhonized 关键字对于使用和正确使用非常重要。听起来您自己的实验证明了这一点。

有关 Java 同步的更多信息:

Java Synchronized Methods

Java Locks and Synchronized Statements

【讨论】:

    【解决方案3】:

    实际上,从 Java 5 开始,您(正式地)在 java.util.concurrent 中有一组替代工具。有关详细信息,请参阅here。正如文章中所详述的,在 Java 语言级别提供的监视器锁定模型具有许多重大限制,并且很难推断何时存在一组复杂的相互依赖的对象和锁定关系,从而使实时锁定成为可能。 java.util.concurrent 库提供了锁定语义,对于有过类似 POSIX 系统经验的程序员来说可能更熟悉

    【讨论】:

      【解决方案4】:

      当然,“同步”只是语法糖——非常有用的语法糖。

      如果你想要无糖 Java 程序,你应该直接用 Java 字节码编写 monitorentermonitorexitlock解锁 VM Specifications 8.13 Locks and Synchronization中引用的操作

      每个对象都有一个锁。 Java 编程 语言没有提供方法 执行单独的锁定和解锁 操作;相反,他们是 由高层隐式执行 总是安排成对的结构 这样的操作正确。 (爪哇 然而,虚拟机提供 单独的 monitorenter 和 monitorexit 实现锁的指令 和解锁操作。)

      同步语句计算一个 引用一个对象;然后 尝试执行锁定操作 在那个物体上并且不继续 进一步直到锁定操作 成功完成。 (一把锁 操作可能会延迟,因为 关于锁的规则可以防止主要 从参与到一些记忆 另一个线程已准备好执行一个 或更多解锁操作。) 已执行锁定操作,则 同步语句的主体是 执行。通常,编译器用于 Java 编程语言确保 由 a 实现的锁定操作 已执行 monitorenter 指令 在执行主体之前 同步语句匹配 通过执行的解锁操作 一个 monitorexit 指令,每当 同步语句完成, 完成是否正常或 突然。

      自动同步的方法 当它是时执行一个锁定操作 被调用;它的主体没有被执行 直到锁定操作有 成功完成。如果方法 是一个实例方法,它锁定 与实例关联的锁 它被调用(即, 将被称为 this 的对象 在方法的执行过程中 身体)。如果方法是静态的,它 锁定与 表示类的类对象 其中定义了方法。如果 方法体的执行是永远 正常完成或 突然,解锁操作是 自动执行相同的 锁。

      最佳实践是,如果一个变量是 永远由一个线程分配,并且 由他人使用或分配,然后全部 访问该变量应该是 包含在同步方法中或 同步语句。

      虽然是 Java 的编译器 编程语言通常 保证锁的结构化使用 (参见第 7.14 节,“同步”), 无法保证所有代码 提交到Java虚拟机 会服从这个属性。 Java虚拟的实现 机器是允许的,但不是必需的 强制执行以下两项 保证结构化锁定的规则。

      令 T 为线程,L 为锁。 那么:

      1. 一个方法中T对L执行的锁操作的次数 调用必须等于 T 对 L 执行的解锁操作 在方法调用期间是否 方法调用完成 正常或突然。

      2. 在方法调用期间的任何时候都不能解锁的数量 T 对 L 执行的操作,因为 方法调用超过 执行的锁定操作数 T 对 L 自方法调用。

      在不太正式的术语中,在方法期间 调用 L 上的每个解锁操作 必须匹配一些前面的锁 对L进行操作。

      注意锁定和解锁 由 Java 自动执行 调用虚拟机时 同步方法被认为是 在调用方法的过程中发生 调用。

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

          猜你喜欢
          • 2020-10-03
          • 2011-03-09
          • 1970-01-01
          • 2022-11-29
          • 1970-01-01
          • 1970-01-01
          • 2013-11-28
          • 2015-09-04
          • 2014-09-18
          相关资源
          最近更新 更多