一、什么是单例设计模式
单例设计模式的定义是只有一个类,并且提供一个全局访问点。
二、适用于那些场景
一个对象即可完成所有工作,无需大量创建对象消耗资源。比如一个长连接,建立起来就不断的发送数据,如果每一个请求都创建一个链接,资源很快就被消耗殆尽。
三、有什么特点
四、单例模式的优缺点
优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统效率,同时也能够严格控制客户对它的访问。
缺点:也正是因为系统中只有一个实例,这样就导致了单例类的职责过重,违背了“单一职责原则”,同时也没有抽象类,这样扩展起来有一定的困难
五、如何创建
方法一 饿汉模式
使用静态常量,在单例类加载的时候就创建了,常见代码如下:
class Singleton1 { //类内部创建实例 private static Singleton1 instance=new Singleton1(); //构造方法私有化,防止通过new方式 创建私立 private Singleton1(){} //对外提供全局同用的唯一调用方法 public static Singleton1 getInstance(){ return instance; } }
这种方式优点缺点都很明显,
优点:类加载的时候实例化,防止对线程同时访问问题。
缺点:类加载时候就实例化,如果没有使用就会造成内存空间的浪费。
方法二懒汉模式
为了解决饿汉模式对于不使用造成内存空间浪费的缺点,又有人提出了懒汉模式,懒汉模式就是在使用时才创建实例,如果不使用就不会创建。
public class Singleton2 { //先声明句柄,但不立即创建实例 private static Singleton2 instance; //构造方法私有化,防止外部通过使用new方式创建实例 private Singleton2(){} //想外部提供全局共享的唯一实例化接口方法 public static Singleton2 getInstance(){ if(instance==null){ instance=new Singleton2(); } return instance; } }
但这种懒汉模式也存在一个很大的问题,它有多线程问题,在多个线程同时访问的时候并不能保证单例,我们使用多线程去获取单例,发现获取的实例并不唯一。
public class mytest { public static void main(String[] args){ long l1=System.currentTimeMillis(); ExecutorService es=Executors.newFixedThreadPool(10); for(int i=0;i<10;i++){ es.execute(new Runnable(){ @Override public void run(){ Singleton2 s2=Singleton2.getInstance(); System.out.println(s2); } }); } es.shutdown(); while(!es.isTerminated()){} long l2=System.currentTimeMillis(); System.out.println(l2-l1); } }
zc.com.examp.test1.core.Singleton2@1ffffbce
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@1ffffbce
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
5
这是因为在执行instance==null语句时,此时还没有创建实例,此时满足条件的线程都进入,执行instance=new Singleton2()语句。对于此问题,我们可以使用synchronized关键字,让getInstance()方法只能同时被一个线程访问。
public class Singleton2 { //先声明句柄,但不立即创建实例 private static Singleton2 instance; //构造方法私有化,防止外部通过使用new方式创建实例 private Singleton2(){} //想外部提供全局共享的唯一实例化接口方法 public static synchronized Singleton2 getInstance(){ if(instance==null){ instance=new Singleton2(); } return instance; } }
测试结果如下:
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
zc.com.examp.test1.core.Singleton2@49ce13ed
9
可以看到通过使用Synchronized关键字确实可以解决多线程同时访问的问题,但同时也降低了执行效率,每次调用getInstance()方法都要涉及锁的操作。其实我们可以对上面的代码进一步优化。
public class Singleton2 { //先声明句柄,但不立即创建实例 private static Singleton2 instance; //构造方法私有化,防止外部通过使用new方式创建实例 private Singleton2(){} //想外部提供全局共享的唯一实例化接口方法 public static Singleton2 getInstance(){ if(instance==null){ //只有在第一次使用的时候构造实例对象,使用synchronized代码块和双重判断避免多线程问题,并且提供效率 synchronized(Singleton2.class){ if(instance==null){ instance=new Singleton2(); } } } return instance; } }
测试结果:
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
zc.com.examp.test1.core.Singleton2@4c75281a
5
这种方式不仅解决了线程不安全问题,又提升了执行效率,只有前面的少数线程可能会获取锁,只要实例被创建,后面的线程一般只需第一个if判断就返回了对象,所以这种方式推荐使用。
方法三内部静态类。
我们知道饿汉模式缺点就是类在加载时候就创建了实例,容易造成内存资源的浪费,如果在单例类中再创建一个静态内部类,外部类装载的时候静态内部类不会装载,只有使用的时候才会装载,因此达成了懒汉式的效果,实现代码如下:
class Singleton3 { //构造方法私有化,防止通过new方式 创建私立 private Singleton3(){} //静态内部类,在外部类加载的时候不会加载静态内部类 private static class SingletonInstance{ static Singleton3 instance=new Singleton3(); } //对外提供全局同用的唯一调用方法 public static Singleton3 getInstance(){ //只有在使用到静态内部类的时候才会加载,并且通过类加载机制保证在初始化的时候只有一个实例产生 return SingletonInstance.instance; } }
-----------------------------------------------------------------------------------------------------------------
参考博文:https://www.cnblogs.com/garryfu/p/7976546.html
参考博文:https://www.cnblogs.com/ye-feng-yu/p/11183075.html
原文:https://www.cnblogs.com/Jerryoned/p/13263750.html