单例模式
单例模式简介
单例模式是一种创建型模式,它提供了一种创建对象的最佳方式。单例类只能有一个实例,并提供一个访问它的全局访问点。
单例模式的优点
- 在内存中只会存在一个实例,这样大大减少了内存的开销,尤其是频繁的创建和销毁实例
- 避免了对资源的多重占用,节约资源。
- 提供了对唯一实例的受控访问。
单例模式的缺点
- 不适用于变化的对象,如果同一类型的对象需要在不同的应用场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
- 没有接口,不利于扩展。
- 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎样实例化。
单例模式的应用场景
- Servlet中每一个servlet的实例
- 配置xml文件时bean对象的获取
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源,数据库软件系统使用数据库连接连接池,主要节省打开和关闭数据库连接所引起的效率损耗。
- 多线程的线程池设计一般也是采用单例模式,这是由于线程池要方便对线程进行控制。
- 应用程序的日志应用一般也是采用单例模式,因为共享的日志文件一直处于打开状态,因此只能有一个实例进行操作,否则容易出问题。
单例模式的实现方式
- 懒汉模式
- 饿汉模式
- 双重校验锁
- 登记式/静态内部类
- 枚举
懒汉模式
package com.jbc.singleton;
/**
* 单例模式-懒汉模式
* 实现了延迟加载
* 线程安全,但是由于在getInstance方法上使用了同步锁,每次调用这个方法都会同步,影响效率
* 不推荐使用
* @author sharbee
*
*/
public class SingletonInstance1 {
//1.声明此类型的变量,但是不实例化
private static SingletonInstance1 instance;
//2.私有化构造方法,避免通过new关键字创建实例
private SingletonInstance1(){}
//3.对外提供一个获取实例的静态方法,加上synchronized关键字,保证数据安全
public static synchronized SingletonInstance1 getInstance1(){
if(instance==null){
return new SingletonInstance1();
}
return instance;
}
}
饿汉模式
package com.jbc.singleton;
/**
* 单例模式-饿汉模式
* 线程安全
* 没有实现延迟加载
* 这种方式是基于类加载机制来解决多线程的问题的,因为类在加载时线程互斥。
* 缺点:只要类加载就会创建实例,浪费内存.
* @author sharbee
*
*/
public class SingletonInstance2 {
//1.声明该类型的变量,并且实例化,在类加载的过程中创建了实例
//由于在类加载的过程中是线程互斥的,所以保证了线程安全
private static SingletonInstance2 instance2 =new SingletonInstance2();
//2.私有化构造方法,防止用new关键字直接创建对象实例
private SingletonInstance2(){}
//创建一个静态方法获取实例。
public static SingletonInstance2 getInstance2(){
return instance2;
}
}
双重校验锁
双重校验锁这里将synchronized关键字写在了if条件内部,解决了饿汉模式下每次调用方法都会实现同步浪费资源的问题。这里一共进行了两次if判断
第一次判断:这里主要是为了解决饿汉模式下浪费资源的问题,进行一次判断,倘若不为null,就不会再进入同步锁中。
第二次判断:假设没有进行第二次判断,那么当前线程t1执行了第一次判断后,判断实例为空,假设线程t2也获取了cpu执行权力进入了方法,第一次判断,实例为空,这时t1又获取了cpu的控制权,获取了锁,创建了实例,释放锁。然后t1又获取cpu的执行权力,由于之前已经对第一个if进行了判断,不再进行判断,获取锁,有创建了实例,就会导致创建多个实例。所以需要在同步块中进行第二次判断,确保只创建一个实例。
注意:
private static volatile SingletonInstance3 instance3=null;需要加volatile关键字,否则会出现错误。问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。
package com.jbc.singleton;
/**
*
* 双检锁/双重校验锁(DCL,即 double-checked locking)
* 线程安全
* 实现了延迟加载
* @author sharbee
*
*/
public class SingletonInstance3 {
//1.声明该类型的变量
private static volatile SingletonInstance3 instance3=null;
//2.私有化构造方法
private SingletonInstance3(){}
//3.创建一个获取实例的静态方法
public static SingletonInstance3 getInstance3(){
if(instance3==null){
synchronized(instance3){
if(instance3==null){
return new SingletonInstance3();
}
}
}
return instance3;
}
}
登记式/静态内部类
package com.jbc.singleton;
/**
* 类似饿汉模式,都是基于类加载机制实现的
* 线程安全
* 有延时加载
* @author sharbee
*
*/
public class SingletonInstance4 {
private static class SingletonClassGetInstance4{
//创建一个实例,加上final关键字,保证这个实例是不变的
private static final SingletonInstance4 instance4=new SingletonInstance4();
}
//私有化构造方法
private SingletonInstance4(){}
public static final SingletonInstance4 getInstance4(){
return SingletonClassGetInstance4.instance4;
}
}
枚举
public enum SingletonInstance5 {
INSTANCE;
public void whateverMethod() {
}
}