java單例的實現有多種,這裏介紹簡單的幾種:
單例都是要求 構造方法私有化
1:內部類模式(推薦使用)
特點:能實現懶加載、是線程安全的,不用鎖實現
/**
* 內部類實現單例
* 0:只初始化一個
* 1:懶加載
* 2:線程安全
*/
public class InnerSingle{
private InnerSingle(){
System.out.println("內部類初始化");
}
public static InnerSingle getInstance(){
return InnerHelper.innerSingle;
}
private static class InnerHelper{
private static InnerSingle innerSingle = new InnerSingle();
}
}
2:餓漢模式 - 怕捱餓,先實例
特點:靜態屬性中新建對象
優點:線程安全、性能也高 缺點:不能懶加載
/**
* 單例 - 餓漢
*/
public class SingleHungry {
private static SingleHungry singleHungry = new SingleHungry();
private SingleHungry(){
System.out.println("初始化");
}
public static SingleHungry getInstance(){
return singleHungry;
}
}
3:懶漢模式 - 用到再實例
普通模式:不用同步關鍵字,爲線程不安全
if(instance == null){
instance = new Single()
}
方法鎖模式:在getInstance方法前加入 同步 關鍵字得意實現線程安全。
特點:在初始化完成後,每次調用都需要去競爭鎖,效率特別慢。
有點:解決了線程安全的問題。
public static synchronized getInstance() {
if(instance == null){
instance = new Single()
}
}
雙檢查模式(重點學習):
加入volatile保證變量在不同線程間的 可見性
判斷有無實例(第一次檢查),只有在沒有實例前才需要去走同步代碼
獲取鎖後,還需要再次判斷是否已經實例化了。(第二次檢查,讀者先思考下這裏爲什麼要第二次檢查,後面有限多例模式有解答)
public class SyncSingle {
/**
* 對保存實例的變量添加volatile的修飾
*/
private volatile static SyncSingle instance = null;
private SyncSingle(){
}
public static SyncSingle getInstance(){
//先檢查實例是否存在,如果不存在才進入下面的同步塊
if(instance == null){
//同步塊,線程安全的創建實例
synchronized(SyncSingle.class){
//再次檢查實例是否存在,如果不存在才真的創建實例
if(instance == null){
instance = new SyncSingle();
}
}
}
return instance;
}
}
有限多例模式
有限多例的意思是最多能實例化多少個,仿照單例模式的 懶漢的雙檢查 方法 來實現。
/**
* 有限的多例 3個
*/
public class ThreeSingle {
private ThreeSingle(){
}
private static volatile List<ThreeSingle> list = new ArrayList<>();
private static final int INSTANCE_MAX = 3;
public static ThreeSingle getInstance(){
if(list.size() < INSTANCE_MAX){
synchronized (ThreeSingle.class){
System.out.println("list size爲"+list.size());
if(list.size() < INSTANCE_MAX){//注意這點,需要重新檢查,因爲可能有多個線程(超過3個)等待鎖,但是隻有前3個線程能建立對象
ThreeSingle threeSingle = new ThreeSingle();
list.add(threeSingle);
System.out.println("add");
}
}
}
// System.out.println(list.size());
return list.get(new Random().nextInt(list.size()));
}
}
測試方法
public static void main(String[] args) {
for (int i =0;i<100;i++){
new Thread(()->{
ThreeSingle.getInstance();
}).start();
}
}
得到的結果爲:
list size爲0
add
list size爲1
add
list size爲2
add
list size爲3
list size爲3
list size爲3
list size爲3
list size爲3
結果中有多個 list size爲3 ,說明在有超過3個線程在等待鎖,獲取鎖之後,可能 list 已經超過3個了,所以這裏需要再次檢查。這就是爲什麼需要雙檢查的原因。
如果你的多線程的知識儲備不夠,建議可以看看筆者的另外一篇博文:https://blog.csdn.net/xiaoluo5238/article/details/104380207