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

Win*_*ter 67 java synchronization

让我用这个小而简单的样本:

class Sample {
    private String msg = null;

    public void newmsg(String x){
        msg = x;
    }

    public String getmsg(){
        String temp = msg;
        msg = null;
        return temp;
    }
}
Run Code Online (Sandbox Code Playgroud)

我们假设该函数newmsg()由我无权访问的其他线程调用.

我想使用synchonize方法来保证字符串msg每次只被一个函数使用.换句话说,功能newmsg()不能同时运行getmsg().

Jon*_*eet 155

这很简单:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我没有使方法本身同步或同步this.我坚信只有你的代码可以访问的对象才能获得锁,这是个好主意,除非你故意暴露锁.这样可以更容易地让自己放心,没有其他任何东西会以与代码不同的顺序获取锁定等.

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

Pet*_*rey 49

对于此功能,最好不要使用锁.尝试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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • FYI在`java.util.concurrent.atomic`中有类用于原始类型,例如`AtomicBoolean`和`AtomicInteger` (6认同)

小智 9

从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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 根据[文档](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantLock.html),锁定应该在 try 块之前完成。 (2认同)

no.*_*ing 5

使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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