一次代碼調試中發現一個情況,即我在查看memcached的connection時,發現總是維持在100來個左右,當然這看似沒什麼問題,因為memcached默認connection有1024個。但是我想的是為什麼會有100來個,因為我的memcachedclient的產生采用的是單例模式我定義了一個memcachedClientFactory類,主要代碼如下:
代碼如下:
MemcachedClientFactory{
private MemcachedConnectionBuilder memcachedConnectionBuilder;
private String servers;
private static MemcachedClient memcachedClient;
private MemcachedClientFactory(){
}
private MemcachedClientFactory(MemcachedConnectionBuilder memcachedConnectionBuilder, String servers){
this. memcachedConnectionBuilder= memcachedConnectionBuilder;
this.servers=servers;
}
public static MemcachedClient createClient(){
if(memcachedClient==null){
this.memcahcedClien= new MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));
}
return this.memcahcedClient;
}
}
}
回到最初的問題,為什麼會有100多個連接?
上面這個寫法真的能保證只產生一個連接?很顯然是不能,為什麼?多線程並發!問題就出在這裡,當有多個線程同時進入createClient()方法時,而且剛好都判斷為memcachedClient為null,這時候就產生了多個連接。哈,問題找到了。
改進:
代碼如下:
public static synchronizd MemcachedClient createClient(){
if(memcachedClient==null){
this.memcahcedClien= new
MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));
}
return this.memcahcedClient;
}
這樣就ok了,改動很簡單。程序是沒有問題了,而且也能保證只有一個連接。
不過拋開這個問題,我們可以繼續就如何解決單例模式下的並發問題深入思考一下。
我總結一下,要解決單例模式在並發下的問題,大概有三種方式:
1. 不使用延遲實例化,而是用提前實例化。
即程序改寫為:
代碼如下:
Public Class Singleton{
private static Singleton instance=new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return instance;
}
}
這樣做時,jvm在加載類時就立馬創建了該實例,所以這樣做的前提是,創建該實例的負擔不大,我不比過多的考慮性能,並且我們確認該實例是一定會用到的。其實我前面的代碼也完全可以使用這個方式:
代碼如下:
MemcachedClientFactory{
private MemcachedConnectionBuilder memcachedConnectionBuilder;
private String servers;
private static MemcachedClient memcachedClien= new
MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));
private MemcachedClientFactory(){
}
private MemcachedClientFactory(MemcachedConnectionBuilder memcachedConnectionBuilder, String servers){
this. memcachedConnectionBuilder= memcachedConnectionBuilder;
this.servers=servers;
}
public static MemcachedClient createClient(){
return this.memcahcedClient;
}
}
}
不過,看上去似乎沒有問題,但是有隱患,即一旦有人不小心調用了memcachedClient.shutdown()方法,那整個程序就無法再生出新的memcachedClient了。當然這是極端情況了,但是為了代碼的健壯,可以再改為:
代碼如下:
public static MemcachedClient createClient(){
if(memcachedClient==null){
this.memcahcedClien= new MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));
}
return this.memcahcedClient;
}
2. 就是使用synchronized關鍵字。
這麼做可以保證同步問題,但是我們知道使用synchronized的開銷是很大的,會嚴重影響性能,所以用這個的前提是,你確認不會經常調用這個方法,或者你創建這個instance的開銷不會特別大。是否還可以改進,看 下面。
3. 使用“雙重檢查加鎖“,在getInstance中見識使用同步
代碼如下:
public Class Singleton{
private volatile static Singleton instance;
private Singleton(){};
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}