2017-05-07 71 views
0

我想了解這是否是線程安全的。我相信是這樣,但最近有人質疑這種方法的線程安全性。瞭解線程安全

比方說,我有一些工廠FactoryA,讓我們它實現了以下接口的類:

public abstract class MyFactory { 
    private ObjectA object; 
    public void init(ObjectA object){ 
     _object = object; 
    } 
} 

因此,我們必須像

public class FactoryA extends MyFactory { 
    static final createFactoryClass(String name) { 
     // ...ignorning safety checks for brevity 
     return MY_MAP.get(name).newInstance(); 
    } 
} 

現在,我已經在另一個一些方法返回可能類別的地圖:

public class OtherClass { 
    private static FactoryA _factory = new FactoryA(); 
    private static final Map<String, SomeClass> MY_MAP = new ImmutableMap.Builder<String, MyClass>() 
    .put("foo", _factory.createFactoryClass("foo")); 

    private SomeObject myMethod(ObjectA objectA, SomeObject someObject) { 
     MY_MAP.get(someObject.getString()).init(objectA); 
    } 
} 

問題是init方法是否是線程安全的。該映射只初始化一次,所以即使它存儲在一個不可變的結構中,如果兩個線程使用不同的ObjectA調用它,錯誤的類是否可能使用錯誤的ObjectA

我可以通過執行以下操作來解決這個問題嗎?

private static synchronized myMethod(...) {}

+0

這是有點令人困惑,因爲代碼中你引用的對象跨類和類型不匹配。但我認爲真正的問題在於你在'init'方法中做了什麼? – Xerillio

+1

您的'MyFactory.init'方法,就像給出的那樣,完全是線程安全的,因爲它什麼都不做。 –

+0

'createFactoryClass()'看起來也是線程安全的。正如你所說,你初始化地圖一次,然後再寫一次。這裏的附帶條件是它仍然必須可見;將它分配給'final'就可以了,並且使地圖(和所有的寫入)可見,就像'volatile'和'synchronized'一樣。 C.F. [「安全出版物」](http://stackoverflow.com/questions/801993/java-multi-threading-safe-publication) – markspace

回答

1

局部變量總是線程安全的,除非該變量是被共享的對象,即,像在下面的例子中的引用:

static void setValue(Example obj, int val) { 
    obj.val = val; 
} 

Example sharedObj = ...; 

new Thread(() -> { setValue(sharedObj, 1); }).start(); 
new Thread(() -> { setValue(sharedObj, 2); }).start(); 

在該例子中,雖然,它不是使用對本身不安全的sharedObj的引用,而是我們使用該引用同時更改sharedObj.val的狀態的事實。

相反,如果我們有具有引用不同的對象兩個線程:

Thread threadA = new Thread(() -> { 
    Example objA = ...; 
    setValue(objA, 1); 
}); 
Thread threadB = new Thread(() -> { 
    Example objB = ...; 
    setValue(objB, 2); 
}); 
threadA.start(); 
threadB.start(); 

JVM將不會感到困惑,例如通過threadA的對象threadB的調用setValue,反之亦然。這就好像你在問什麼,而這不會發生。每個線程都有自己的調用堆棧,並且每個線程的調用setValue在其自己的線程上打開一個新的堆棧幀。換句話說,threadA調用setValue,它在threadA上用自己的本地變量創建一個堆棧幀,threadB調用setValue,它在threadB上用它自己的局部變量創建一個堆棧幀。

還有一個獨立的問題,那就是你的init方法所做的更改可能不會被其他線程看到。例如,假設你有一個Thread thread1,它的工作是通過將對象傳遞給init來初始化對象,然後將這些對象傳遞給其他Thread thread2。將thread2看到initthread1上的對象所做的更改?答案是,沒有某種內存同步,thread2可能看不到在thread1撥打init期間所做的更改。

儘管同步製作myMethod可能並不能解決這個問題。相反,thread1thread2需要某種方式進行通信,如共享監視器對象或鎖。

還有第三個問題,我猜你在暗示,這是關於地圖的初始化。如果地圖是靜態初始化期間只建成,那麼它是線程安全的,因爲class initialization is performed under synchronization和:

實現可以通過eliding獲取鎖優化此過程[...]當它能夠確定類的初始化已經完成,但是,就內存模型而言,所有的事情都會發生 - 在獲得鎖的情況下存在的排序之前,在執行優化時仍然存在。

換句話說,靜態初始化是保證其他線程可以看到,即使在JVM決定,它已經一段時間,因爲一個特定的類已經初始化,並確定需要讀取字段,不試圖獲取初始化鎖。

-1

我會建議不同的類/方法的命名,因爲目前很難理解正在發生什麼。我也會把工廠變成單身人士。

import java.util.HashMap; 
import java.util.Map; 


public class FactoryStore { 

private static final Map<String, AbstractFactory> FACTORIES = new HashMap<>(); 

static { 
    FACTORIES.put("a", FactoryA.getInstance()); 
    FACTORIES.put("b", FactoryB.getInstance()); 
} 

public static Object createObject(String factoryName, Object parameters) { 
    return FACTORIES.get(factoryName).createNewObject(parameters); 
} 
} 



abstract class AbstractFactory { 

Object createNewObject(Object parameters) { 
    // not thread-safe stuff 
    return new Object(); 
} 


} 



class FactoryA extends AbstractFactory { 

private static final FactoryA instance = new FactoryA(); 

private FactoryA() { 
    // thread safe stuff 
} 

public static FactoryA getInstance() { 
    return instance; 
} 


} 


class FactoryB extends AbstractFactory { 

private static final FactoryB instance = new FactoryB(); 

private FactoryB() { 
    // thread safe stuff 
} 

public static FactoryB getInstance() { 
    return instance; 
} 

@Override 
synchronized Object createNewObject(Object obj) { 
    // can override object creation; this is thread-safe thanks to keyword 
    return new Object(); 
} 

}