單例大概是我最早產生明確模式意識的設計模式,因為它足夠簡單粗暴,目的足夠明確。
單例么,就是不管怎么訪問,都返回一個單一實例就好了,我最早應用在數據庫的DBUtil中。
1 public class DBUtils 2 { 3 4 //驅動串 5 private final static String Driver="com.mysql.jdbc.Driver"; 6 //連接串 7 private static String url="jdbc:mysql://"+ SysProperties.MYSQL_HOST+":"+SysProperties.MYSQL_PORT+"/"+SysProperties.MYSQL_NAME+"?autoReconnect=true&autoReconnectForPools=true"; 8 //Connection 9 private static Connection conn; 10 11 12 13 private DBUtils() 14 { 15 } 16 17 18 19 public static Connection getConnection()throws Exception 20 { 21 url="jdbc:mysql://"+SysProperties.MYSQL_HOST+":"+SysProperties.MYSQL_PORT+"/"+SysProperties.MYSQL_NAME; 22 CE.lgInfo("url:" + url); 23 24 if(conn==null || conn.isClosed()) 25 { 26 conn=DriverManager.getConnection(url, SysProperties.MYSQL_USER, SysProperties.MYSQL_PWD); 27 } 28 29 return conn; 30 } 31 32 }
很完美,在我的小程序里跑了近兩年。
可是有的時候檢查訂單,就會發現每個階段的訂單都有那么幾塊錢無論如何對不上。這一直是個幽靈bug,困擾許久。
知道有一天,檢查日志的的時候發現,當一個數據庫在訪問數據庫進行鎖表的時候,被鎖內容卻被另一個操作修改掉了。於是,我第一次看到了線程的影子。
但是那時候我對線程的理解完全不夠透徹,只是模糊知道:
- 可能是有好幾個請求同時訪問了這個方法;
- 在一個請求要new Connection的時候,將new未new之際,另一個請求插隊了,也擠進來了;
- 第二個請求一看,哦,沒有connection,我也new一個,其實此時第一個請求已經在new了,只不過還沒new出來而已;
- 結果就出來了兩個Connection,單例?呵呵……
然后知道了有個關鍵字叫synchronized, 好,改。
1 public static synchronized Connection getConnection()throws Exception 2 { 3 url="jdbc:mysql://"+SysProperties.MYSQL_HOST+":"+SysProperties.MYSQL_PORT+"/"+SysProperties.MYSQL_NAME; 4 CE.lgInfo("url:" + url); 5 6 if(conn==null || conn.isClosed()) 7 { 8 conn=DriverManager.getConnection(url, SysProperties.MYSQL_USER, SysProperties.MYSQL_PWD); 9 } 10 11 return conn; 12 }
於是變成了這樣。於是,同一時間只有一個線程可以訪問這個方法,其他人想訪問,好,你等着吧,等我搞完你再上。
所有對該方法的請求被JVM強制排起了隊,自然不會被new出來多個Connection了。
跑了半年,似乎不錯。
但是后來忽然想,我之所以使用synchronized,是不想讓他重復new,而不是不想讓他重復訪問。感覺就像什么呢,大家去生孩子,為了讓醫生好好工作,讓大家都要排隊進產房,這很正常,可是我現在除了讓大家排隊進產房,還讓大家排隊懷孕……這就扯淡了。
於是查資料,知道了synchronized不僅可以作用於方法,還可作用於區塊。於是有了下面這個:
1 public static synchronized Connection getConnection()throws Exception 2 { 3 url="jdbc:mysql://"+SysProperties.MYSQL_HOST+":"+SysProperties.MYSQL_PORT+"/"+SysProperties.MYSQL_NAME; 4 CE.lgInfo("url:" + url); 5 6 if(conn==null || conn.isClosed()) 7 { 8 synchronized (DBUtil.class){ 9 if(conn==null || conn.isClosed()){ 10 conn=DriverManager.getConnection(url, SysProperties.MYSQL_USER, SysProperties.MYSQL_PWD); 11 } 12 } 13 } 14 15 return conn; 16 }
只有為null的時候,才排隊。為啥判斷兩次?更安全啊。
當然,后來知道了他有個更高大上的名字,稱為“雙重檢查鎖”。
所以,一個單例,真的簡單么?可以用最快速的方法實現一個單例,也可以用相對更安全的方法實現一個單例。
如果你在面試,一個單例寫下來,就知道你是一個菜鳥還是老手了。