6

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

4

5 回答 5

14

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

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

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

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

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

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

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

有关 Java 同步的更多信息:

Java 同步方法

Java 锁和同步语句

于 2010-06-26T17:02:13.240 回答
1

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

于 2010-06-26T23:13:50.843 回答
1

当然,“同步”只是语法糖- extremley 有用的语法糖。

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

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

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

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

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

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

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

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

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

用不太正式的术语来说,在方法调用期间,对 L 的每个解锁操作都必须匹配对 L 的某个先前的锁定操作。

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

于 2010-06-27T00:13:02.570 回答
0

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

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

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

于 2010-06-26T18:54:32.477 回答