![](/img/trans.png)
[英]Java EE - Singleton EJB with Concurrent Access to Synchronized Method
[英]Synchronized access on a singleton
我有一個關於單例同步的問題。 假設我有一個單例模式,如下所示:
public class Singleton {
private final Object obj;
private static Singleton instance;
private Singleton(Object obj) {
this.obj = obj;
}
public static synchronized Singleton getInstance(Object obj) {
if (instance == null) {
instance = new Singleton(obj);
}
return instance;
}
public synchronized void methodA() {
// ...
}
public synchronized void methodB() {
// ...
}
}
我想同步對其的訪問。 我有兩個假設,需要驗證。
假設:由於所有方法(包括初始化程序)都已同步,因此對該Singleton的每次訪問都是線程安全和同步的。
假設:當我想要確保的是要調用一個線程methodA()
然后立即methodB()
沒有其他線程調用單的方法,是正確的在這樣的情況下同步?
Singleton singleton = Singleton.getInstance(obj);
synchronized (singleton) {
singleton.methodA();
singleton.methodB();
}
說明:
1.假設是否正確,因為在非靜態方法上同步是在對象本身上同步,並且由於它始終是同一對象,所以訪問是同步的? 並且對getInstance(obj)
的調用是否在類本身上同步?
2.假設是否正確,因為每個線程都使用getInstance(obj)
獲取相同的對象,因此同步是正確的,因為另一個線程將等待直到退出同步塊( ...methodA(); methodB();
)?
假設我理解正確,您的假設是正確的。 我只想指出,在大多數情況下,您可以使用更簡單的單例模式:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
該字段將以線程安全的方式在第一個靜態引用(例如getInstance()
)上初始化,而無需顯式同步。
同樣, instance
應該是final
。
您的第一個假設是正確的,但是,在專用鎖對象上進行synchronized
而不是使用synchronized
方法是一個好習慣。 這樣做可以將並發問題排除在API之外,並允許您稍后使用java.util.concurrent包替換鎖,以實現更有效的實現。 您還可以防止死鎖,因為其他對象無法獲取相同的鎖對象。
public class Example {
// leaving out the singleton aspect here
// consider java.util.concurrent.locks instead
private Object lock = new Object();
public synchronized void methodA() {
synchronized(lock) {
// ...
}
}
public synchronized void methodB() {
synchronized(lock) {
// ...
}
}
}
客戶端仍然可以在實例上同步:
synchronized(instance) {
instance.methodA();
instance.methodB();
}
例如,查看java.util.Collections
類中SynchronizedCollection
的實現,該類也使用內部鎖定對象。
與單例同步有關的一個問題:如果您的應用程序中有多個並發線程(例如,在Web應用程序中),則由於所有線程都必須互相等待,因此此中央鎖很快就會成為性能瓶頸。
兩者都是對的。
最好的測試方法(測試解決方案總是好的,而不是依賴於假設)將是在調用方法之間等待一段時間,然后從不同的線程調用methodA,然后檢查執行順序。 例如:
public synchronized void methodA() {
println("methodA");
}
public synchronized void methodB() {
println("methodB");
}
然后,在測試用例或主要方法中:
new Thread(){
public void run(){
Singleton singleton = Singleton.getInstance();
synchronized (singleton) {
singleton.methodA();
singleton.wait(5000L);//this gives us 5 seconds to call it from different thread
singleton.methodB();
}
}
}.start();
new Thread(){
public void run(){
Singleton.getInstance().methodA();
}
}
}.start();
如您所知,synchronized關鍵字用於獲取對象上的鎖。 如果要在執行一種方法之后調用自願方法。 然后,我們可以像下面的代碼一樣從同步方法中調用該方法,但是線程要等到它從第一個同步塊中出來后才會釋放。
public synchronized void method1A() {
// ......
method1B();
}
public synchronized void method1B() {
// ...
}
在這里,線程將獲取最后輸入到同步方法1A中的鎖,它將調用另一個同步方法1B。 線程將在method1A執行完成后釋放鎖。
你們兩個假設都是正確的。 但是,對於創建Singleton,我將建議以下方法是100%線程安全的,並且沒有同步問題,因為它利用了Java的類加載機制。
像此類一樣,Provider將僅被加載一次,因此將僅存在Network類的一個實例,並且由於類不會被加載兩次,因此沒有兩個線程可以在同一JVM中創建2個實例。
public class Network {
private Network(){
}
private static class Provider {
static final Network INSTANCE = new Network();
}
public static Network getInstance() {
return Provider.INSTANCE;
}
//More code...
}
線程安全單例樣本;
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
return instance;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.