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

发布于 2024-09-07 03:19:30 字数 971 浏览 2 评论 0 原文

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

I am new to multithreading, and I wrote this code which prints the numbers 1-10000 by having concurrently running threads increment and print a variable.

Here's the code I'm using:

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

This works - I wrote up a test program to check the output, and the numbers printed are exactly 1-10000 in order.

My question is this: I've heard that synchronized is only syntactic sugar. But I can't seem to achieve a successful result without using it. What am I missing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

蓝色星空 2024-09-14 03:19:30

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

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

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

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

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

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

synchronized is by no means syntactic sugar for anything. There is no way to work locks in Java without using the synchronized keyword.

Where there is "syntactic sugar" of a sort in locks in Java is that synchronized can apply both to blocks (as you've done it above) and to whole methods. The following two methods are roughly equivalent in semantics:

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

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

So why would you want to do the second version instead of the first?

  • Synchronized method invocations are far slower than plain old method invocations, like by about an order of magnitude. If your synchronized code isn't guaranteed to always execute (say it's in a conditional), then you probably don't want to synchronize the whole method.
  • Synchronized methods hold locks for longer than synchronized blocks (because of all the method setup/tear down code). The second method above will hold the lock for less time because the time spent setting up and tearing down the stack frame won't be locked.
  • You can have much finer control over exactly what you're locking if you go with the synchronized blocks.
  • (Courtesy of starblue) Synchronized blocks can use objects other than this for locking which gives you more flexible locking semantics.
海夕 2024-09-14 03:19:30

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

有关 Java 同步的更多信息:

Java 同步方法

Java 锁和同步语句

It sounds like your sources are just wrong. The syncrhonized keyword is important to use - and use properly - when writing thread-safe code. And it sounds like your own experiments bear this out.

For more on synchronization in Java:

Java Synchronized Methods

Java Locks and Synchronized Statements

情感失落者 2024-09-14 03:19:30

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

Actually as of Java 5 you (formally) have an alternative set of tools in java.util.concurrent. See here for more details. As detailed in the article the monitor locking model provided at Java's language level has a number of significant limitations and can be difficult to reason about when there are a complex set of interdependent objects and locking relationships making live-lock a real possibility. The java.util.concurrent library offers locking semantics which might be more familiar to programmers who've had experience in POSIX-like systems

埋情葬爱 2024-09-14 03:19:30

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

如果您想要无糖 Java 程序,您应该直接用 Java 字节代码编写 monitorentermonitorexitlock解锁 VM 规范中引用的操作8.13 锁和同步

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

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

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

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

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

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

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

  2. 在方法调用过程中任何时候都不能解锁次数
    自 T 对 L 执行的操作
    方法调用超过
    执行的锁定操作数
    自方法调用以来,T 位于 L 上。

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

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

Of course, "synchronized" is just syntactic sugar - extremley useful syntactic sugar.

If you want sugar-free java programs, you should be writing directly in java byte code the monitorenter, monitorexit, lock, and unlock operations referenced in VM Specifications 8.13 Locks and Synchronization

There is a lock associated with every object. The Java programming
language does not provide a way to
perform separate lock and unlock
operations; instead, they are
implicitly performed by high-level
constructs that always arrange to pair
such operations correctly. (The Java
virtual machine, however, provides
separate monitorenter and monitorexit
instructions that implement the lock
and unlock operations.)

The synchronized statement computes a
reference to an object; it then
attempts to perform a lock operation
on that object and does not proceed
further until the lock operation has
successfully completed. (A lock
operation may be delayed because the
rules about locks can prevent the main
memory from participating until some
other thread is ready to perform one
or more unlock operations.) After the
lock operation has been performed, the
body of the synchronized statement is
executed. Normally, a compiler for the
Java programming language ensures that
the lock operation implemented by a
monitorenter instruction executed
prior to the execution of the body of
the synchronized statement is matched
by an unlock operation implemented by
a monitorexit instruction whenever the
synchronized statement completes,
whether completion is normal or
abrupt.

A synchronized method automatically
performs a lock operation when it is
invoked; its body is not executed
until the lock operation has
successfully completed. If the method
is an instance method, it locks the
lock associated with the instance for
which it was invoked (that is, the
object that will be known as this
during execution of the method's
body). If the method is static, it
locks the lock associated with the
Class object that represents the class
in which the method is defined. If
execution of the method's body is ever
completed, either normally or
abruptly, an unlock operation is
automatically performed on that same
lock.

Best practice is that if a variable is
ever to be assigned by one thread and
used or assigned by another, then all
accesses to that variable should be
enclosed in synchronized methods or
synchronized statements.

Although a compiler for the Java
programming language normally
guarantees structured use of locks
(see Section 7.14, "Synchronization"),
there is no assurance that all code
submitted to the Java virtual machine
will obey this property.
Implementations of the Java virtual
machine are permitted but not required
to enforce both of the following two
rules guaranteeing structured locking.

Let T be a thread and L be a lock.
Then:

  1. The number of lock operations performed by T on L during a method
    invocation must equal the number of
    unlock operations performed by T on L
    during the method invocation whether
    the method invocation completes
    normally or abruptly.

  2. At no point during a method invocation may the number of unlock
    operations performed by T on L since
    the method invocation exceed the
    number of lock operations performed by
    T on L since the method invocation.

In less formal terms, during a method
invocation every unlock operation on L
must match some preceding lock
operation on L.

Note that the locking and unlocking
automatically performed by the Java
virtual machine when invoking a
synchronized method are considered to
occur during the calling method's
invocation.

青丝拂面 2024-09-14 03:19:30

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

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

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

Synchronization is one of the most important concepts while programming in multi thread environment.
While using synchronization one has to consider the object over which synchronization takes place.
For example if a static method is to be synchronized then the synchronization must be on the class level using

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

if the block in instance method is to be synchronized then the synchronization must be using an instance of an object which is shared between all the threads.
Most poeple go wrong with the second point as it appears in your code where the synchronization appears to be on different objects rather than a single object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文