單例模式看着一篇就夠了
問題:
多個線程操作不同實例對象,現在需要多個線程要操作同一對象,要保證對象的唯一性。
解決問題:
實例化過程中只實例化一次
單例模式屬於創造型模式,是常用的設計模式之一。
單例模式(Singleton),保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
如何區分餓漢式和懶漢式:在自己被加載時就將自己實例化,稱之爲餓漢式單例類,而要是在第一次被引用時,纔會將自己實例化,稱之爲懶漢式。
餓漢式
線程安全:在加載的時候(ClassLoader) 已經被實例化,所以只有一次,線程安全的。
懶加載:沒有延遲加載,對於長時間不使用,影響性能,如果佔的內存多會導致內存溢出
性能:比較好
package sheji;
/**
* @author zhr
* @create 2018-11-15 下午4:02
*/
public class Singleton {
//static 在加載的時候就產生了實例對象
private static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("這兩個對象是相同的實例");
}
}
}
控制檯打印:這兩個對象是相同的實例
懶漢式
線程安全:不能保證實例對象的唯一性,可能出現多個線程同時進入到instance==null中 生成多個對象(一票否決不要用)
懶加載:有延遲加載
性能:好
跟餓漢式比:優化了在調用過程的時候纔去實例化,但又出現了新的問題那就是線程安全的問題
/**
* @author zhr
* @create 2018-11-15 下午3:59
*/
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("這兩個對象是相同的實例");
}
}
}
控制檯打印:這兩個對象是相同的實例
懶漢式+同步方法
線程安全:可以保證實例唯一性
懶加載:有延遲加載
性能:不好,因爲使用了synchronized 多個線程訪問的會後就會蛻化到了串行執行
跟懶漢式比:線程安全了,但是鎖的粒度太大性能損失很大
/**
* @author zhr
* @create 2018-11-15 下午4:00
*/
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public synchronized static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("這兩個對象是相同的實例");
}
}
}
控制檯打印:這兩個對象是相同的實例
懶漢式+同步代碼塊
線程安全:不能保證實例唯一
懶加載:有延遲加載
性能:一般
懶漢式+同步方法比較:我們鎖的粒度雖然減小了,但是在併發的環境下當,多個線程同時進入到instance==null的時候第一個實例創建對象,釋放鎖的時候第二個線程可能就會創建對象,從而導致線程不安全
/**
* @author zhr
* @create 2018-11-15 下午4:07
*/
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
instance = new Singleton();
}
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("這兩個對象是相同的實例");
}
}
}
控制檯打印:這兩個對象是相同的實例
雙重校驗鎖 DCL
線程安全:可以保證實例唯一性
懶加載:有延遲加載
性能:比較好
問題:因爲happens-before指令重排,裏面有多個引用對象需要初始化的時候,會出現空指針異常
/**
* @author zhr
* @create 2019-09-14 下午3:08
*/
public class Singleton {
private static Singleton instance;
Connection conn;
Socket socket;
private Singleton(){
//初始化conn 和 socket 這裏指令重排 把初始化放在前面了,導致第一個線程初始化了,第二個線程進來認爲已經初始化了,然後獲取了conn就會出現空指針異常
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("這兩個對象是相同的實例");
}
}
}
控制檯打印:這兩個對象是相同的實例
Volatile+雙重校驗鎖 DCL
線程安全:可以保證實例唯一性
懶加載:有延遲加載
性能:比較好(推薦使用)
解決DCL指令重排問題方案是機上volatile使初始化前的東西不能被指令重排就可以了。
/**
* @author zhr
* @create 2019-09-14 下午3:10
*/
public class Singleton {
private volatile static Singleton instance;
Connection conn;
Socket socket;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("這兩個對象是相同的實例");
}
}
}
控制檯打印:這兩個對象是相同的實例
Holder持有者
聲明類的時候,成員變量中不聲明實例變量,而放到內部靜態類中,在內部靜態類中我們提供實例化這個類,主要是爲了避免加鎖,只有主動使用的時候纔會去進行實例化
線程安全:可以保證實例唯一性
懶加載:有延遲加載
性能:比較好(廣泛使用)
/**
* @author zhr
* @create 2019-09-14 下午3:20
*/
public class HolderDemo{
//可以刪除
private HolderDemo(){
}
private static class Holder{
//懶加載 沒加鎖
private static HolderDemo instance = new HolderDemo();
}
public static HolderDemo getInstance(){
return Holder.instance;
}
}
控制檯打印:這兩個對象是相同的實例
枚舉
線程安全:可以保證實例唯一性
懶加載:沒有延遲加載
性能:比較好(廣泛使用)
評價:,其實跟餓漢式很像,代碼簡潔優雅
/**
* @author zhr
* @create 2019-09-14 下午3:25
*/
public enum EnumSingleton{
//常量,在加載的時候被實例化一次
INSTANCE;
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
枚舉+Holder
線程安全:可以保證實例唯一性
懶加載:有延遲加載
性能:比較好(廣泛使用)
/**
* @author zhr
* @create 2019-09-14 下午3:30
*/
public class EnumSingletonDemo{
private EnumSingletonDemo(){
}
//靜態內部類 只有在使用的時候纔會加載實例化
private enum EnumHolder{
//常量,在加載的時候被實例化一次,方法區
INSTANCE;
private EnumSingletonDemo instance;
EnumHolder(){
this.instance = new EnumSingletonDemo();
}
private EnumSingletonDemo getInstance(){
return instance;
}
}//懶加載
public static EnumSingletonDemo getInstance(){
return EnumHolder.INSTANCE.getInstance();
//return EnumHolder.INSTANCE.instance;
}
}
如果看完感覺受益匪淺,可以幫忙點點關注,點點贊,還會持續更新好的作品。