최종 클래스가 아닌 필드에서 동기화 할 때마다 경고가 표시됩니다. 다음은 코드입니다.
public class X
{
private Object o;
public void setO(Object o)
{
this.o = o;
}
public void x()
{
synchronized (o) // synchronization on a non-final field
{
}
}
}
그래서 다음과 같은 방식으로 코딩을 변경했습니다.
public class X
{
private final Object o;
public X()
{
o = new Object();
}
public void x()
{
synchronized (o)
{
}
}
}
위의 코드가 최종 클래스가 아닌 필드에서 동기화하는 적절한 방법인지 모르겠습니다. 최종 필드가 아닌 필드를 어떻게 동기화 할 수 있습니까?
답변
우선, 더 높은 수준의 추상화에서 동시성 문제를 처리하기 위해 열심히 노력할 것을 권장합니다. 즉 ExecutorServices, Callables, Futures 등과 같은 java.util.concurrent의 클래스를 사용하여 해결하는 것입니다 .
즉, 최종 필드가 아닌 필드 자체에서 동기화하는 것은 잘못된 것이 아닙니다 . 객체 참조가 변경되면 동일한 코드 섹션이 병렬로 실행될 수 있음 을 명심해야 합니다 . 즉, 한 스레드가 동기화 된 블록의 코드를 실행하고 누군가를 호출 setO(...)
하면 다른 스레드가 동일한 인스턴스 에서 동일한 동기화 된 블록을 동시에 실행할 수 있습니다 .
독점 액세스가 필요한 개체 (또는 보호 전용 개체)를 동기화합니다.
답변
정말 좋은 생각이 아니다 – 동기화 된 블록을 더 이상하기 때문에 정말 일관성있는 방법으로 동기화하지 않습니다.
동기화 된 블록이 한 번에 하나의 스레드 만 일부 공유 데이터에 액세스하도록 보장하기위한 것이라고 가정하면 다음을 고려하십시오.
- 스레드 1이 동기화 된 블록에 들어갑니다. 예-공유 데이터에 독점적으로 액세스 할 수 있습니다 …
- 스레드 2는 setO ()를 호출합니다.
- 스레드 3 (또는 여전히 2 …)이 동기화 된 블록에 들어갑니다. Eek! 공유 데이터에 대한 배타적 액세스 권한이 있다고 생각하지만 스레드 1은 여전히 이동 중입니다 …
왜 것입니다 원하는 이 일이? 아마도 거기에 몇 가지 가 의미가 매우 전문 상황 …하지만 당신은 내가 만족하실 것입니다 전에 (내가 위에서 준 시나리오의 종류를 완화하는 방법과 함께) 특정 사용 케이스에 저를 제시해야 할 것 그것.
답변
나는 John의 의견 중 하나에 동의합니다 . 변수의 참조가 변경되는 경우 불일치를 방지하기 위해 비 최종 변수에 액세스하는 동안 항상 최종 잠금 더미를 사용해야합니다. 따라서 어떤 경우에도 첫 번째 경험 법칙 :
규칙 # 1 : 필드가 최종이 아닌 경우 항상 (개인) 최종 잠금 더미를 사용하십시오.
이유 # 1 : 잠금을 유지하고 직접 변수의 참조를 변경합니다. 동기화 된 잠금 외부에서 대기중인 다른 스레드는 보호 된 블록에 들어갈 수 있습니다.
이유 # 2 : 잠금을 유지하고 다른 스레드가 변수의 참조를 변경합니다. 결과는 동일합니다. 다른 스레드가 보호 된 블록에 들어갈 수 있습니다.
그러나 최종 잠금 더미를 사용할 때 또 다른 문제 가 있습니다. 동기화 (object)를 호출 할 때 최종이 아닌 개체는 RAM 과만 동기화되기 때문에 잘못된 데이터를 얻을 수 있습니다. 따라서 두 번째 경험 법칙으로 :
규칙 # 2 : 최종 잠금이 아닌 개체를 잠글 때 항상 두 가지를 모두 수행해야합니다. RAM 동기화를 위해 최종 잠금 더미와 최종 잠금이 아닌 개체의 잠금을 사용합니다. (유일한 대안은 객체의 모든 필드를 휘발성으로 선언하는 것입니다!)
이러한 잠금을 “중첩 잠금”이라고도합니다. 항상 동일한 순서로 호출해야합니다. 그렇지 않으면 데드락이 발생합니다 .
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
//do something with o...
}
}
}
}
보시다시피 두 자물쇠는 항상 함께 속하기 때문에 동일한 줄에 직접 작성합니다. 이와 같이 10 개의 중첩 잠금을 수행 할 수도 있습니다.
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
//entering the locked space
}
}
}
}
이 코드는 synchronized (LOCK3)
다른 스레드에서 와 같이 내부 잠금을 획득하는 경우 중단되지 않습니다 . 그러나 다음과 같은 다른 스레드를 호출하면 중단됩니다.
synchronized (LOCK4) {
synchronized (LOCK1) { //dead lock!
synchronized (LOCK3) {
synchronized (LOCK2) {
//will never enter here...
}
}
}
}
최종이 아닌 필드를 처리하는 동안 이러한 중첩 된 잠금에 대한 해결 방법은 하나뿐입니다.
규칙 # 2-대안 : 개체의 모든 필드를 휘발성으로 선언합니다. (여기서는이 작업의 단점에 대해 설명하지 않을 것입니다. 예를 들어 읽기를위한 x 레벨 캐시의 스토리지 방지, aso.)
따라서 aioobe가 옳습니다. java.util.concurrent를 사용하십시오. 또는 동기화에 대한 모든 것을 이해하고 중첩 된 잠금을 사용하여 직접 수행하십시오. 😉
최종 필드가 아닌 필드의 동기화가 중단되는 이유에 대한 자세한 내용은 내 테스트 사례를 살펴보십시오. https://stackoverflow.com/a/21460055/2012947
RAM 및 캐시로 인해 동기화가 필요한 이유에 대한 자세한 내용은 https://stackoverflow.com/a/21409975/2012947을 참조 하십시오.
답변
나는 여기서 정답을 실제로 보지 못하고 있습니다. 즉, 그것을 하는 것은 완벽하게 괜찮습니다.
왜 경고인지 모르겠지만 아무 문제가 없습니다. JVM은 당신이 얻을 있는지 확인합니다 일부 는 값을 읽을 때 유효한 오브젝트 다시 (또는 null), 당신은에 동기화 할 수 있는 객체입니다.
잠금을 사용하는 동안 실제로 변경하려는 경우 (예 : 사용을 시작하기 전에 init 메서드에서 변경하는 것과 반대) 변경할 계획 인 변수를 만들어야합니다 volatile
. 그런 다음 이전 개체와 새 개체 를 모두 동기화하기 만하면 값을 안전하게 변경할 수 있습니다.
public volatile Object lock;
…
synchronized (lock) {
synchronized (newObject) {
lock = newObject;
}
}
그곳에. 복잡하지 않고 잠금 (뮤텍스)을 사용하여 코드를 작성하는 것은 실제로 매우 쉽습니다. 그것들없이 코드를 작성하는 것은 어려운 일입니다.
답변
편집 : 따라서이 솔루션 (Jon Skeet이 제안한대로)은 개체 참조가 변경되는 동안 “synchronized (object) {}”구현 원자성에 문제가있을 수 있습니다. 개별적으로 물었고 erickson 씨에 따르면 스레드로부터 안전하지 않습니다. 참조 : 동기화 된 블록 원자로 들어가는가? . 그래서 그것을하지 않는 방법을 예로 들어보십시오-왜 링크와 함께;)
synchronised ()가 원자 적이면 어떻게 작동하는지 코드를 참조하십시오.
public class Main {
static class Config{
char a='0';
char b='0';
public void log(){
synchronized(this){
System.out.println(""+a+","+b);
}
}
}
static Config cfg = new Config();
static class Doer extends Thread {
char id;
Doer(char id) {
this.id = id;
}
public void mySleep(long ms){
try{Thread.sleep(ms);}catch(Exception ex){ex.printStackTrace();}
}
public void run() {
System.out.println("Doer "+id+" beg");
if(id == 'X'){
synchronized (cfg){
cfg.a=id;
mySleep(1000);
// do not forget to put synchronize(cfg) over setting new cfg - otherwise following will happend
// here it would be modifying different cfg (cos Y will change it).
// Another problem would be that new cfg would be in parallel modified by Z cos synchronized is applied on new object
cfg.b=id;
}
}
if(id == 'Y'){
mySleep(333);
synchronized(cfg) // comment this and you will see inconsistency in log - if you keep it I think all is ok
{
cfg = new Config(); // introduce new configuration
// be aware - don't expect here to be synchronized on new cfg!
// Z might already get a lock
}
}
if(id == 'Z'){
mySleep(666);
synchronized (cfg){
cfg.a=id;
mySleep(100);
cfg.b=id;
}
}
System.out.println("Doer "+id+" end");
cfg.log();
}
}
public static void main(String[] args) throws InterruptedException {
Doer X = new Doer('X');
Doer Y = new Doer('Y');
Doer Z = new Doer('Z');
X.start();
Y.start();
Z.start();
}
}
답변
AtomicReference 는 귀하의 요구 사항에 적합합니다.
원자 패키지 에 대한 Java 문서에서 :
단일 변수에 대한 잠금없는 스레드 안전 프로그래밍을 지원하는 작은 클래스 툴킷입니다. 본질적으로이 패키지의 클래스는 휘발성 값, 필드 및 배열 요소의 개념을 다음 형식의 원자 조건부 업데이트 작업도 제공하는 것으로 확장합니다.
boolean compareAndSet(expectedValue, updateValue);
샘플 코드 :
String initialReference = "value 1";
AtomicReference<String> someRef =
new AtomicReference<String>(initialReference);
String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);
위의 예에서 String
자신의Object
관련 SE 질문 :
답변
o
의 인스턴스 수명 동안 변경되지 않는 경우 X
동기화 관련 여부에 관계없이 두 번째 버전이 더 나은 스타일입니다.
이제 첫 번째 버전에 문제가 있는지 여부는 해당 클래스에서 다른 일이 벌어지고 있는지 모른 채 대답 할 수 없습니다. 나는 컴파일러가 오류가 발생하기 쉽다는 것에 동의하는 경향이 있습니다 (다른 사람들이 말한 것을 반복하지 않을 것입니다).