Как синхронизировать или блокировать переменные в Java?
Позвольте мне использовать этот небольшой и простой пример:
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()
.
Ответы
Ответ 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
. Я твердо верю, что это хорошая идея, чтобы получить только блокировки объектов, к которым имеет доступ только ваш код, если вы не намеренно раскрываете блокировку. Это намного облегчает уверенность в себе, что ничто другое не собирается приобретать блокировки в другом порядке для вашего кода и т.д.
Ответ 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);
}
}
Никаких блокировок не требуется, а код проще ИМХО. В любом случае он использует стандартную конструкцию, которая делает то, что вы хотите.
Ответ 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();
}
}
}
Ответ 4
В этом простом примере вы можете просто поместить synchronized
в качестве модификатора после public
в обеих сигнатурах методов.
Более сложные сценарии требуют других вещей.
Ответ 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;
}
}
Использование ключевого слова synchronized
для методов потребует потоков для получения блокировки в экземпляре sample
. Таким образом, если какой-либо один поток находится в newmsg()
, ни один другой поток не сможет получить блокировку экземпляра sample
, даже если он пытался вызвать getmsg()
.
С другой стороны, использование методов synchronized
может стать узким местом, если ваши методы выполняют длительные операции - все потоки, даже если они хотят вызывать другие методы в этом объекте, которые могут чередоваться, все равно придется ждать.
IMO, в вашем простом примере, нормально использовать синхронизированные методы, поскольку у вас на самом деле есть два метода, которые не должны чередоваться. Тем не менее, при различных обстоятельствах может возникнуть больше смысла синхронизировать объект блокировки, как показано в ответе Joh Skeet.
Ответ 6
Если в другом случае вы синхронизируете коллекцию, а не строку, возможно, вы выполняете итерацию по коллекции и беспокоитесь о ее мутации, Java 5 предлагает: