【单例模式定义】
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
【关键点】
1.定义一个private访问权限的构造方法,避免被其它类new出一个对象。
2.自己可以new出一个对象,其他类可以通过getInstance()方法获得同一个对象。
【优点】
1.由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
2.单例模式只生成一个实例,减少了系统的性能开销,当一个对象产生需要较多资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动的时候直接产生一个单例对象,然后永久驻留内存的方式来解决。
3.单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个文件同时写操作。
4.单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如一个单例类来负责所有的数据表的映射处理。
【使用场景】
(在一个系统中,要求类有且只有一个对象的场景)
1.要求生成唯一序列号的环境。
2.在整个项目中需要一个共享访问点或共享数据。
3.创建一个对象需要消耗的资源资源过多,如要访问IO和数据库等资源。
4.需要定义大量的静态常量和静态方法(常见于工具类)的环境。(当然也可以直接声明为static的方式)
【饿汉式】
package com.Higgin.Single; public class HungrySingle { // private static final HungrySingle hungrySingle=new HungrySingle(); //限制产生多个对象 private HungrySingle(){};
//通过该方法获得实例对象 public static HungrySingle getInstance(){ return hungrySingle; } //类中的其它方法,尽量使static public static void doSomething(){ } }
【懒汉式:线程不安全】
package com.Higgin.Single; /** * 线程不安全的 懒汉式 单例模式 */ public class LazySingle { private LazySingle(){}; public static LazySingle lazySingle=null; public static LazySingle getInstance(){ if(lazySingle==null){ lazySingle=new LazySingle(); } return lazySingle; } }
[ 普通懒汉式:线程不安全的例子 ]
class LazySingle { private LazySingle(){}; public static LazySingle lazySingle=null; public static LazySingle getInstance() throws InterruptedException{ if(lazySingle==null){ Thread.sleep(1000); //这里刻意延迟1秒,增加多个线程进入该判断的几率 lazySingle=new LazySingle(); } return lazySingle; } } class MyThread implements Runnable{ @Override public void run() { try { for(int i=0;i<10;i++){ LazySingle l = LazySingle.getInstance();//每个线程都去获取10次单例模式的实例 System.out.println(Thread.currentThread().getName()+"======"+l.hashCode()); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Test{ public static void main(String[] args) { MyThread t= new MyThread(); //开启四个线程 new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } }
[ 运行结果 ]
其hashcode值有多处不同,说明发生了多线程的处理异常
【懒汉式:双重加锁(线程安全)】
/** * 懒汉式:双重加锁 */ public class LazySingle { private LazySingle(){} private static LazySingle singleton=null; public static LazySingle getInstance(){ if(singleton==null){ //第一个null判断 synchronized(LazySingle.class){ //同步代码块,确保这里只有一个进程进入代码块 if(singleton==null){ //第二个null判断 singleton=new LazySingle(); } } } return singleton; } }
[ 为什么要加第一个null判断 ]
加第二个null判断很容易理解,为什么要加第一个null判断呢?
对于单例设计模式,本质上要求我们只能执行一次" singleton=new LazySingle(); ",
如果没有第一个null判断,所有线程都会来抢占锁,抢占成功的正常执行,未成功的要进行等待,每次只能有一个线程执行。
如果加上第一个null判断,只要执行了一次" singleton=new LazySingle(); ",后续进入该方法的线程,无需等待同步代码块的锁,只要在第一个null判断执行结束后就可以往下走了,极大节约了性能。
[ 验证加第一个null判断的性能 ]
/** * 懒汉式:双重加锁 */ class LazySingle { private LazySingle(){} private static LazySingle singleton=null; public static LazySingle getInstance() throws InterruptedException{ if(singleton==null){ //第一个null判断 synchronized(LazySingle.class){ Thread.sleep(1); //延迟1ms使性能的效果更明显 System.out.println("进入同步..."); if(singleton==null){ //第二个null判断 Thread.sleep(1000); singleton=new LazySingle(); } } } return singleton; } } class MyThread implements Runnable{ @Override public void run() { try { for(int i=0;i<10;i++){ LazySingle l = LazySingle.getInstance();//每个线程都去获取10次单例模式的实例 System.out.println(Thread.currentThread().getName()+"======"+l.hashCode()); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Test { //线程 启动 并且 插队 public static void startJoin(Thread t){ try { t.start(); //启动线程 t.join(); //线程插队 } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { MyThread t= new MyThread(); long begin=System.currentTimeMillis(); //创建多个插队的线程 for(int j=0;j<100;j++){ startJoin(new Thread(t)); } LazySingle l=LazySingle.getInstance(); //获取单例模式的 System.out.println("main方法的线程======"+l.hashCode()); long end=System.currentTimeMillis(); System.out.println("【耗时:"+(end-begin)+" 毫秒】"); } }
[ 运行结果 ]
[ 注释了第一个null之后的运行结果(性能下降) ]
【静态内部类方式:线程安全】
/** * 懒汉式:静态内部类 线程安全 */ public class LazySingle { private LazySingle(){} private static class LazySingleBuilder{ private static LazySingle singleton=new LazySingle(); } public static LazySingle getInstance(){ return LazySingleBuilder.singleton; } }
[ 分析 ]
当getInstance()方法第一次被调用的时候,第一次读取LazySingleBuilder.singleton,静态内部类LazySingleBuilder类得到初始化,在这个类装载并初始化的时候,会初始化它的静态域,从而创建LazySingle的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并且由虚拟机来保证它的线程安全性。