单例模式(思惟导图)

图1 Java单例模式【点击查看大图】java

  单例模式:【核心】只存在一个实例化对象编程

  单例模式确保某个类只有一个实例,并且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具备资源管理器的功能。每台计算机能够有若干个打印机,但只能有一个Printer Spooler,以免两个打印做业同时输出到打印机中。每台计算机能够有若干通讯端口,系统应当集中管理这些通讯端口,以免一个通讯端口同时被两个请求同时调用。总之,选择单例模式就是为了不不一致状态,避免政出多头。设计模式

  Singleton经过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的惟一实例只能经过getInstance()方法访问。缓存

  (事实上,经过Java反射机制是可以实例化构造方法为private的类的,那基本上会使全部的Java单例实现失效。此问题在此处不作讨论,姑且掩耳盗铃地认为反射机制不存在。)

安全

1,饿汉式

 1 package com.cnblogs.mufasa.Demo3;
 2 
 3 public class Singleton {
 4     private static Singleton instance=new Singleton();
 5     private Singleton(){
 6         System.out.println("饿汉式 无参构造");
 7     }
 8     public static Singleton getInstance(){
 9         return instance;
10     }
11     public void print(){
12         System.out.println("饿汉式【单例设计模式】");
13     }
14 }
View Code

调用就直接进行实例化多线程

单例调用测试:并发

 1 package com.cnblogs.mufasa.Demo3;
 2 
 3 public class Demo {
 4     public static void main(String[] args) throws Exception {
 5         long startTime =  System.currentTimeMillis();
 6         //多线程状况下
 7         for(int x=0;x<1000;x++){
 8             new Thread(()->{
 9                 Singleton.getInstance().print();
10             },"单例消费端-"+x).start();
11         }
12 
13         long endTime =  System.currentTimeMillis();
14         long usedTime = (endTime-startTime);
15         System.out.println("耗时:"+usedTime);//143
16 
17     }
18 }
View Code

 

2,普通懒汉式【非线程安全

 1 package com.cnblogs.mufasa.Demo3;
 2 //普通懒汉式,多线程会出现多个实例化
 3 //多线程状况下,就不是单例模式了,没有进行synchronized
 4 public class Singleton1 {
 5     private static Singleton1 instance=null;
 6     private String str="";
 7     private Singleton1(){
 8         System.out.println("【"+Thread.currentThread().getName()+"】单线程下的单例模式");
 9     }
10     public static Singleton1 getInstance(){
11         if(instance==null){
12             instance=new Singleton1();
13         }
14         return instance;
15     }
16     public void print(){
17         System.out.println(str+"懒汉式单例模式");
18     }
19 
20     public String getStr() {
21         return str;
22     }
23 
24     public void setStr(String str) {
25         this.str = str;
26     }
27 }
View Code

 须要的时候才进行实例化,多线程的时候会出现实例化多个对象的状况!!!框架

3,初级同步懒汉式

 1 package com.cnblogs.mufasa.Demo3;
 2 //代价很大,之前的并发变成串行,效率下降
 3 public class Singleton2 {
 4     private static Singleton2 instance = null;
 5     private Singleton2(){
 6         System.out.println("【"+Thread.currentThread().getName()+"】单线程下的单例模式");
 7     }
 8     public static synchronized Singleton2 getInstance(){
 9         if(instance==null){
10             instance=new Singleton2();
11         }
12         return instance;
13     }
14     public void print(){
15         System.out.println("懒汉式【单例设计模式】添加getInstance同步修饰");
16     }
17 }
View Code

 对getInstance进行synchronized修饰,可是这个把本来的并发操做进行串行转换,低效ide

4,高级同步懒汉式【无volatile修饰】

 1 package com.cnblogs.mufasa.Demo3;
 2 //双重效验锁
 3 //只是在处理空对象时,才进行同步操做,只有一次是同步其余所有是并发非同步处理
 4 public class Singleton3 {
 5 //    private static volatile Singleton3 instance=null;
 6     private static Singleton3 instance = null;//添加volatile修饰符
 7     private Singleton3(){
 8         System.out.println("【"+Thread.currentThread().getName()+"】*** 单线程下的【单例模式】 ***");
 9     }
10     public static Singleton3 getInstance(){//仍是并发处理
11         if(instance==null){
12             synchronized (Singleton3.class){
13                 if(instance==null){
14                     instance=new Singleton3();
15                 }
16             }
17         }
18         return instance;
19     }
20     public void print(){
21         System.out.println("懒汉式【单例设计模式】");
22     }
23 }
View Code

 也叫作双重效验锁,进行两次判断,而且第二次以前进行同步操做处理测试

接下来我解释一下在并发时,双重校验锁法会有怎样的情景:

STEP 1. 线程A访问getInstance()方法,由于单例尚未实例化,因此进入了锁定块。

STEP 2. 线程B访问getInstance()方法,由于单例尚未实例化,得以访问接下来代码块,而接下来代码块已经被线程1锁定。

STEP 3. 线程A进入下一判断,由于单例尚未实例化,因此进行单例实例化,成功实例化后退出代码块,解除锁定。

STEP 4. 线程B进入接下来代码块,锁定线程,进入下一判断,由于已经实例化,退出代码块,解除锁定。

STEP 5. 线程A获取到了单例实例并返回,线程B没有获取到单例并返回Null

5,volatile修饰懒汉式

 1 package com.cnblogs.mufasa.Demo3;
 2 //双重效验锁
 3 //只是在处理空对象时,才进行同步操做,只有一次是同步其余所有是并发非同步处理
 4 public class Singleton3 {
 5     private static volatile Singleton3 instance=null;
 6 //    private static Singleton3 instance = null;//添加volatile修饰符
 7     private Singleton3(){
 8         System.out.println("【"+Thread.currentThread().getName()+"】*** 单线程下的【单例模式】 ***");
 9     }
10     public static Singleton3 getInstance(){//仍是并发处理
11         if(instance==null){
12             synchronized (Singleton3.class){
13                 if(instance==null){
14                     instance=new Singleton3();
15                 }
16             }
17         }
18         return instance;
19     }
20     public void print(){
21         System.out.println("懒汉式【单例设计模式】");
22     }
23 }
View Code

 保持内存可见性和防止指令重排序,直接跳过副本对内存进行操做

6,Holder式

 1 package com.cnblogs.mufasa.Demo3;
 2 
 3 public class Singleton4 {
 4     private static class SingletonHolder{
 5         private static Singleton4 instance=new Singleton4();
 6     }
 7     private Singleton4(){
 8         System.out.println("无参构造");
 9     }
10     public static Singleton4 getInstance(){
11         return SingletonHolder.instance;
12     }
13     public void print(){
14         System.out.println("holder模式实现的线程安全单例"+Thread.currentThread().getName());
15     }
16 }
View Code

 静态类内部加载,经过调用内部静态类——内部静态中的静态属性初始化——来建立对象

7,枚举法实现

1 package com.cnblogs.mufasa.Demo3;
2 
3 enum  Singleton5 {
4     INSTANCE;
5     public void print(){
6         System.out.println("枚举法实现的线程安全单例模式"+Thread.currentThread().getName());
7     }
8 }
View Code

 利用枚举自己特性实现,(1)自由串行化。(2)保证只有一个实例。(3)线程安全。

枚举与其余的单例调用方式不太同样:

 1 package com.cnblogs.mufasa.Demo3;
 2 
 3 public class Demo5 {
 4     public static void main(String[] args) {
 5         long startTime =  System.currentTimeMillis();
 6         //多线程状况下
 7         for(int x=0;x<1000;x++){
 8             new Thread(()->{
 9                 Singleton5.INSTANCE.print();
10             },"单例消费端-"+x).start();
11         }
12         long endTime =  System.currentTimeMillis();
13         long usedTime = (endTime-startTime);
14 
15         System.out.println("耗时:"+usedTime);//144
16     }
17 }
View Code

 

8,ThreadLocal实现

 1 package com.cnblogs.mufasa.Demo3;
 2 //使用ThreadLocal实现单例模式(线程安全)
 3 public class Singleton6 {
 4     private Singleton6(){}
 5     private static final ThreadLocal<Singleton6> tlSingleton=new ThreadLocal<>(){
 6         @Override
 7         protected Singleton6 initialValue(){
 8             return new Singleton6();
 9         }
10     };
11 
12     public static Singleton6 getInstance(){
13         return tlSingleton.get();
14     }
15 
16     public static void print(){
17         System.out.println("ThreadLocal实现单例模式");
18     }
19 
20 }
View Code

 每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突

经过牺牲空间换取时间——原始数据是一样的可是咱们不使用原始数据,咱们使用它的拷贝副本,避免各个线程争抢资源【之前有一个球你们都抢来抢去,如今给你们伙每人发一个,你们都不抢了】!有点像原型模式的策略

9,CAS锁实现

 1 package com.cnblogs.mufasa.Demo3;
 2 
 3 import java.util.concurrent.atomic.AtomicReference;
 4 
 5 //使用CAS锁实现(线程安全)
 6 public class Singleton7 {
 7     private static final AtomicReference<Singleton7> INSTANCE = new AtomicReference<Singleton7>();
 8     private Singleton7(){
 9     }
10     public static final Singleton7 getInstance(){
11         for(;;){
12             Singleton7 current=INSTANCE.get();
13             if(current!=null){
14                 return current;
15             }
16             current=new Singleton7();
17             if(INSTANCE.compareAndSet(null,current)){
18                 System.out.println("同步锁比较");
19                 return current;
20             }
21         }
22     }
23     public static void print(){
24         System.out.println("CAS锁实现单例模式");
25     }
26 
27 }
View Code

 经过编程的原子操做来进行同步

(1)使用总线锁保证原子性【一个CPU操做共享变量内存地址的缓存的时候,锁住总线其余CPU不能插手——变成串行化操做,一个个的来】

(2)使用缓存锁保证原子性【内存区域若是被缓存在处理器的缓存行中,而且在Lock操做期间被锁定,那么当它执行锁操做回写到内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并容许它的缓存一致性机制来保证操做的原子性,由于缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其余处理器回写已被锁定的缓存行的数据时,会使缓存行无效】

(3)Java使用循环CAS实现原子操做

https://blog.csdn.net/zxx901221/article/details/83033998

 

 10,登记式单例

  Spring的IOC-实现中就使用的在这种策略,Definition中缓存的的实例,下次再使用的时候直接调用便可。

package com.cnblogs.mufasa.Demo3;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class Singleton8 {
    private static Map<String,Singleton8> map=new HashMap<String, Singleton8>();
    static {
        Singleton8 singleton=new Singleton8();
        map.put(singleton.getClass().getName(),singleton);
    }
    protected Singleton8(){}
    public static Singleton8 getInstance(String name){
        if(name==null){
            name=Singleton8.class.getName();
            System.out.println("name == null"+"--->name="+name);
        }
        if(map.get(name)==null){
            try {
                map.put(name,(Singleton8) Class.forName(name).getDeclaredConstructor().newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return map.get(name);
    }
    public void print(){
        System.out.println("利用反射&类名注册机制实现单例");
    }
}
View Code

利用反射和类名注册实现的单例模式

package com.cnblogs.mufasa.Demo3;

public class Demo8 {
    public static void main(String[] args) {
        long startTime =  System.currentTimeMillis();
        //多线程状况下
        for(int x=0;x<1000;x++){
            new Thread(()->{
                Singleton8.getInstance("com.cnblogs.mufasa.Demo3.Singleton8").print();
            },"单例消费端-"+x).start();
        }
        long endTime =  System.currentTimeMillis();
        long usedTime = (endTime-startTime);

        System.out.println("耗时:"+usedTime);//143
    }
}
View Code

 

 11,总结

请编写单例设计模式

·【100%】直接编写一个饿汉式的单例模式,而且实现构造方法私有化;

·【120%】在Java中哪里使用到单例设计模式?Runtime类、Pattern类、Spring框架;

·【200%】懒汉式单例设计模式问题?多线程状况下,可能产生多个实例化对象,违法了其单例的初衷!

 

12,如何破解单例模式?

单例模式是只能产生一个实例化对象,构造方法私有化,不能经过普通的方法进行实例化。

  若是想要获取新的实例化对象,要怎么办呢?

  ①直接跳过无视私有化构造:反射机制

  ②我压根不新创建一个实例化对象,跳过私有化构造,我直接进行开辟新空间的数据深拷贝:原型模式

  以上两种方法均可以无视单例模式,获取多个实例化对象。

 

13,参考连接

连接1:https://blog.csdn.net/jason0539/article/details/23297037

连接2:https://blog.csdn.net/qq_35860138/article/details/86477538

相关文章
相关标签/搜索