单例模式
什么是单例模式
单例模式:确保一个类只有一个实例,并提供一个全局访问点。
我们把某个类设计成自己管理的一个单独实例,同时也避免其他类再自行产生实例。同时提供该实例的全局访问点,当你需要实例时,向类查询,会返回单个实例。
如何实现
平时我们需要对象时,都是new一个出来。这次单例设计模式,通过new的方式,每次出来的对象都不是同一个了,不符合单例模式设计原则。new对象是通过
类自身的公开的构造方法,我们可以通过将构造方式私有不让别人调用,然后提供一个静态方法让需要的人来获取该实例对象。根据该实例对象创建时间,可以分为
懒汉式和饿汉式。
饿汉式:
1 //饿汉式 2 public class Single { 3 private static Single single = new Single(); 4 private Single(){} 5 public static Single getInstance(){ 6 return single; 7 } 8 }
懒汉式:
1 //懒汉式 2 public class Single { 3 private static Single single; 4 private Single(){} 5 public static Single getInstance(){ 6 if(single == null){ 7 single = new Single(); 8 } 9 return single; 10 } 11 }
利弊:饿汉式在程序运行时就创建好了单例对象,这个对象可能会一直用不上,但一开始就被创建了占用资源,造成浪费。懒汉式是有人要用到这个单例对象时才把它创建出来,达到了
资源利用的最合理化,但是他不是线程安全的。看下面的代码就知道了,私有的构造方法被调用了两次,也就是说没有实现输出单例。
1 public class Client { 2 public static void main(String[] args){ 3 for(int i=0;i<2;i++){ 4 //创建两个线程,去获取单例 5 new Thread(){ 6 public void run(){ 7 Singleton.getInstance(); 8 } 9 }.start(); 10 } 11 } 12 } 13 14 class Singleton { 15 private static Singleton singleton; 16 private Singleton(){ 17 System.out.println("我被创建了!"); 18 try { 19 //模拟复杂对象创建时,耗费的时间 20 Thread.sleep(1000); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 } 25 public static Singleton getInstance(){ 26 if(singleton == null){ 27 singleton = new Singleton(); 28 } 29 return singleton; 30 } 31 } 32 33 运行结果: 34 我被创建了! 35 我被创建了!
如果我们将getInstance方法变成同步方法,就可以避免多线程问题。像下面这样
1 public static synchronized Singleton getInstance(){ 2 if(singleton == null){ 3 singleton = new Singleton(); 4 } 5 return singleton; 6 } 7 8 运行结果: 9 我被创建了!
通过增加synchronized关键字到getInstance()方法中,我们迫使每个线程在进入这个方法之前,要先等别的线程离开该方法,这样就没有两个线程同时进入这个方法了。虽然同步解决了线程安全这个问题,但是同步会降低性能,这又是另外一个问题。实际上只有第一次执行getInstance()方法时,才真正需要同步,一旦实例被创建完毕之后,就不需要同步了。优化一下代码
1 public static Singleton getInstance(){ 2 if(singleton == null){ 3 synchronized (Singleton.class){ 4 if(singleton == null){ 5 singleton = new Singleton(); 6 } 7 } 8 } 9 return singleton; 10 } 11 12 运行结果: 13 我被创建了!
这方法叫做“双重检查加锁”,在getInstance()中减少使用同步。
静态内部类实现单例模式,可以兼顾线程安全和懒加载。
1 //这种方式,线程安全,调用效率高,并且实现了延迟加载 2 class Singleton { 3 private static class SingletonClassInstance{ 4 private static final Singleton singleton = new Singleton(); 5 } 6 private Singleton(){ 7 System.out.println("我被创建了!"); 8 try { 9 //模拟复杂对象创建时,耗费的时间 10 Thread.sleep(1000); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 public static Singleton getInstance(){ 16 return SingletonClassInstance.singleton; 17 } 18 }
运行结果:
我被创建了!
要点:外部类没有static属性,不会像饿汉式那样立即加载对象;只有调用getInstance才会加载静态内部类,而加载类是天然的线程安全的,singleton是static final类型,保证了内存中只有一个实例存在;兼备了并发高效调用和延迟加载的优势。
应用场景
1、Windows的Task Manager(任务管理器)就是典型的单例模式
2、网站的计数器也是单例,不然不好同步
3、应用程序的日志应用,一般都使用单例模式,因为共享的日志文件一直处于打卡状态,只能一个实例去操作,否则不好追加
4、Spring中,每个Bean默认就是单例,这样可以方便被Spring容器管理
5、spring MVC框架中,控制器对象也是单例