如何在 Java 中同步或锁定变量?

Posted

技术标签:

【中文标题】如何在 Java 中同步或锁定变量?【英文标题】:How to synchronize or lock upon variables in Java? 【发布时间】:2011-08-17 05:47:47 【问题描述】:

让我用这个小而简单的例子:

class Sample 
    private String msg = null;

    public void newmsg(String x)
        msg = x;
    

    public String getmsg()
        String temp = msg;
        msg = null;
        return temp;
    

假设函数newmsg() 被我无权访问的其他线程调用。

我想使用synchonize方法来保证字符串msg每次只被一个函数使用。也就是说,函数newmsg()不能和getmsg()同时运行。

【问题讨论】:

你在问如何在Java中使用“同步”关键字吗?一个简单的谷歌搜索返回了很多有用的点击,包括这个download.oracle.com/javase/tutorial/essential/concurrency/… 顺便说一句,调用 getmsg() 方法会更好,比如 popmsg() 或 consumemsg(),因为它会修改类 【参考方案1】:

这很简单:

class Sample 
    private String message = null;
    private final Object lock = new Object();

    public void newMessage(String x) 
        synchronized (lock) 
            message = x;
        
    

    public String getMessage() 
        synchronized (lock) 
            String temp = message;
            message = null;
            return temp;
        
    

请注意,我没有使方法本身同步或在this 上同步。我坚信只获取只有您的代码可以访问的对象的锁是个好主意,除非您故意公开锁。它使您更容易向自己保证,没有其他东西会以与您的代码不同的顺序获取锁,等等。

【讨论】:

设置同步功能和创建这个锁有什么区别? @Giacomo:我喜欢我的代码是可读的,并且设计得让我可以推理它。当涉及多线程时,这一点非常重要。毫不夸张地说,这是我几乎总是使用的那种代码。至于是否浪费...您认为这种浪费多久会很严重? @Giacomo:关键是很明显,您正在锁定其他人无法锁定的东西。这似乎相当直截了当:你阅读了代码,你就完成了。现在考虑您的示例:您如何知道可能在您的对象上同步?您如何理解锁定行为?您必须阅读所有所涉及的代码。因此,如果您实际上对正确的行为感到困扰,我会说它的可读性较差。坦率地说,我认为允许 all 对象在 Java 中被锁定是错误的。 (不幸的是,.NET 复制了一个错误。) @Winter 同步方法阻止“this”,因此 Sample 对象本身。但是对象“锁定”上的同步块将阻止“锁定”,而不是样本 @PhilipRego:首先,因为那样被锁定的值会发生变化,这会使事情在各种方面变得不安全。其次,因为这很容易意味着多个对象之间无意共享锁,从而导致各种其他问题。第三:关注点分离。我非常热衷于有一个目的的领域。【参考方案2】:

对于这个功能,你最好不要使用锁。试试 AtomicReference。

public class Sample 
    private final AtomicReference<String> msg = new AtomicReference<String>();

    public void setMsg(String x) 
        msg.set(x);
    

    public String getMsg() 
        return msg.getAndSet(null);
    

不需要锁,代码更简单恕我直言。在任何情况下,它都使用标准构造来满足您的需求。

【讨论】:

仅供参考,java.util.concurrent.atomic 中有用于基本类型的类,例如 AtomicBooleanAtomicInteger【参考方案3】:

从 Java 1.5 开始,考虑 java.util.concurrent 包总是一个好主意。它们是目前 Java 中最先进的锁定机制。同步机制比 java.util.concurrent 类更重量级。

示例如下所示:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Sample 

    private final Lock lock = new ReentrantLock();

    private String message = null;

    public void newmsg(String msg) 
        lock.lock();
        try 
            message = msg;
         finally 
            lock.unlock();
        
    

    public String getmsg() 
        lock.lock();
        try 
            String temp = message;
            message = null;
            return temp;
         finally 
            lock.unlock();
        
    

【讨论】:

根据docs,锁定应该在try块之前完成。【参考方案4】:

使用synchronized 关键字。

class sample 
    private String msg=null;

    public synchronized void newmsg(String x)
        msg=x;
    

    public synchronized string getmsg()
        String temp=msg;
        msg=null;
        return msg;
    

在方法上使用synchronized 关键字将要求线程获得sample 实例上的锁。因此,如果任何一个线程在newmsg() 中,则没有其他线程能够获得sample 实例的锁定,即使它试图调用getmsg()

另一方面,如果您的方法执行长时间运行的操作,使用synchronized 方法可能会成为瓶颈 - 所有线程,即使它们想要调用该对象中可能交错的其他方法,仍然必须等待.

IMO,在您的简单示例中,可以使用同步方法,因为您实际上有两个不应该交错的方法。但是,在不同的情况下,使用锁对象进行同步可能更有意义,如 Joh Skeet 的回答所示。

【讨论】:

【参考方案5】:

在这个简单的示例中,您可以在两个方法签名中将 synchronized 作为修饰符放在 public 之后。

更复杂的场景需要其他东西。

【讨论】:

【参考方案6】:

如果在其他情况下您正在同步一个集合而不是一个字符串,那么您可能正在对集合进行迭代并且担心它会发生变异,Java 5 提供了:

CopyOnWriteArrayList CopyOnWriteArraySet

【讨论】:

以上是关于如何在 Java 中同步或锁定变量?的主要内容,如果未能解决你的问题,请参考以下文章

适当使用同步或锁定一段代码

使用 jQuery 发出同步请求时,如何防止浏览器锁定(或显示等待符号)?

在JAVA中ArrayList如何保证线程安全

java线程同步和锁定没有效果?

java并发知识合集(前置知识——java内存模型)

Java同步静态方法:锁定对象或类