1.單例設計模式的八種方法
餓漢式(靜態常量)
餓漢式(靜態代碼塊)
- 懶漢式(線程不安全)
- 懶漢式(線程安全,同步方法)
- 懶漢式(線程安全,同步代碼塊)
雙重檢查
靜態內部類
枚舉
2.餓漢式(靜態常量)
public class SingletonTest1 {
public static void main(String[] args) {
Singleton1 instance1 = Singleton1.getInstance();
Singleton1 instance2 = Singleton1.getInstance();
System.out.println(instance1 == instance2); //true
}
}
/**
* 餓漢式(靜態變量的方式)
*/
class Singleton1 {
//私有靜態變量,上來就創建對象
private final static Singleton1 instance = new Singleton1();
//私有構造方法
private Singleton1() {
}
//提供公有的供外部訪問的方法
public static Singleton1 getInstance() {
return instance;
}
}
優點:
- 寫法簡單,類裝載的時候就完成實例化,
基於classloader機制避免線程同步問題
缺點:
- 在類裝載時就實例化,沒有達到lazy loading的效果。如果從始至終未用到這個類,就會造成內存的浪費
總結:
這種單例模式可用,但
可能
造成內存浪費
3.餓漢式(靜態代碼塊)
public class SingletonTest2 {
public static void main(String[] args) {
Singleton2 instance1 = Singleton2.getInstance();
Singleton2 instance2 = Singleton2.getInstance();
System.out.println(instance1 == instance2); //true
}
}
/**
* 餓漢式(靜態代碼塊的方式)
*/
class Singleton2 {
//私有靜態變量,僅做定義
private static Singleton2 instance;
//靜態代碼塊中實例化
static {
instance = new Singleton2();
}
//私有構造方法
private Singleton2() {
}
//提供公有的供外部訪問的方法
public static Singleton2 getInstance() {
return instance;
}
}
4.懶漢式(線程不安全-單線程可用)
public class SingletonTest {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
}
}
/**
* 餓漢式
*/
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
//爲空時才創建對象,起到懶加載的效果
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
優點:
起來懶加載的效果
缺點:
只能在單線程下使用。
原因如下:
如果在多線程下,一個線程進入了if (instance == null)判斷語句塊,
還未來得及向下執行,另一個線程也通過了這個判斷語句,這時就會產生多個實例
,所以在多線程下不可使用此種方式
驗證demo
public class SingletonTest {
public static void main(String[] args) {
// Singleton instance1 = Singleton.getInstance();
// Singleton instance2 = Singleton.getInstance();
// System.out.println(instance1 == instance2);
new Thread(() ->{
try {
Singleton instance3 = Singleton.getInstance();
System.out.println(Thread.currentThread().getName()+":hashcode-"+instance3.hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() ->{
try {
Singleton instance3 = Singleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+instance3.hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
/**
* 餓漢式
*/
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() throws InterruptedException {
//爲空時才創建對象,起到懶加載的效果
if (instance == null) {
Thread.sleep(1000);
instance = new Singleton();
}
return instance;
}
}
總結:
不要採用,會出現線程安全問題
5.懶漢式(線程安全,同步方法)
實現方式僅僅是在public static Singleton getInstance()上增加一個synchronized
關鍵字
public class SingletonTest {
public static void main(String[] args) throws InterruptedException {
// Singleton instance1 = Singleton.getInstance();
// Singleton instance2 = Singleton.getInstance();
// System.out.println(instance1 == instance2);
new Thread(() ->{
try {
Singleton instance3 = Singleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+instance3.hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() ->{
try {
Singleton instance3 = Singleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+instance3.hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
/**
* 餓漢式-獲取對象實例的方法加鎖
*/
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() throws InterruptedException {
//爲空時才創建對象,起到懶加載的效果
if (instance == null) {
Thread.sleep(1000);
instance = new Singleton();
}
return instance;
}
}
- 解決了線程不安全的問題
- 效率太低了。每個線程在獲得類的實例時候,執行getInstance()方法都要進行同步。而
其實這個方法只執行一次實例化代碼就夠了
,後面的想獲得該類實例,直接return就行了。方法進行同步效率太低。- 實際開發不推薦
6.懶漢式(線程不安全,同步代碼塊)
public class SingletonTest {
public static void main(String[] args) throws InterruptedException {
// Singleton instance1 = Singleton.getInstance();
// Singleton instance2 = Singleton.getInstance();
// System.out.println(instance1 == instance2);
new Thread(() ->{
try {
Singleton instance3 = Singleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+instance3.hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() ->{
try {
Singleton instance3 = Singleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+instance3.hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
/**
* 餓漢式
*/
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() throws InterruptedException {
//爲空時才創建對象,起到懶加載的效果
if (instance == null) {
synchronized (Singleton.class){
Thread.sleep(1000);
instance = new Singleton();
}
}
return instance;
}
}
並不能解決線程安全問題
7.雙重檢查(線程安全並推薦)
/**
* 餓漢式-雙重檢查優化同步代碼塊線程不安全的問題
*/
class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() throws InterruptedException {
//爲空時才創建對象,起到懶加載的效果
if (instance == null) {
//爲null時才加鎖,保證只有一條線程創建對象
synchronized (Singleton.class) {
if (instance == null) {
Thread.sleep(1000);
instance = new Singleton();
}
}
}
return instance;
}
}
兩次進行if 檢查,保證線程安全。這樣實例化代碼只用執行一次,並且保證只能由一個線程執行,後面再次訪問是,再次判斷if是否爲nul。直接return實例化對象,避免了反覆進行方法同步。
- 線程安全,延遲加載,效率較高
- 實際開發推薦
8.靜態內部類(推薦)
特點是,
外部類裝載時內部類不會被裝載
,只有在調用內部類的相關方法和屬性時,纔會被裝載,並且只被裝載一次
/**
* 靜態內部類
*/
class Singleton {
// 定義私有的靜態內部類
private static class SingletonInnerInstance {
private static final Singleton SINGLETON_INSTANCE = new Singleton();
}
private Singleton() {
}
// 返回靜態內部生成的外部類對象
public static Singleton getInstance() {
return SingletonInnerInstance.SINGLETON_INSTANCE;
}
}
這種方式採用了類裝載的機制來保證初始化實例時只有一個線程
9.枚舉(推薦)
public class SingletonTest {
public static void main(String[] args) {
SingletonEnum instance1 = SingletonEnum.INSTANCE;
SingletonEnum instance2 = SingletonEnum.INSTANCE;
System.out.println(instance1 == instance2);
}
}
/**
* 利用枚舉實現單例
*/
enum SingletonEnum {
INSTANCE;
public void method1() {
System.out.println("OK...");
}
}
- 避免多線程同步問題
防止反序列化重新創建新的對象
- Effective Java提倡。推薦使用
10.JDK源碼分析
11.單例模式-總結
- 單例節省了系統資源,對於一些需要
頻繁創建和銷燬
的對象,使用單例模式可以提高系統性能 - 當想實例化一個單例類的時候,必須要記住使用響應的獲取對象的方法,而不是直接new
- 使用場景:
- 需要頻繁創建和銷燬的對象
- 創建對象時耗時過多或耗費資源過多(重量級對象),但又經常用到的對象、
工具類對象
、頻繁訪問數據庫或文件的對象(比如數據源、session工廠
等)
12.個人應用實戰
將工具類修改爲單例模式-------
使用雙重檢查、靜態內部類都可以
要改爲單例的工具類最好是那種類似數據庫連接池工具類那種,涉及到配置、可擴展那種。否則不如使用靜態方法的方式