loong - 设计模式-单例设计模式
前言
单例模式是指内存中有且只会创建一次对象的设计模式,在程序多次使用同一个对象且作用相同的时候,为了放置频繁的创建对象,单例模式可以让程序在内存中创建一个对象,让所有的调用者都共享这一单例对象,单例对象的类型有两种:懒汉式和饿汉式.
使用场景
- 需要频繁的进行创建和销毁的对象
- 创建对象时耗时过长或者资源多(即:重量级对象)但又经常用到的对象,工具类对象,频繁访问数据库或文件的对象(比如数据源,session工厂等)
单例模式类型
-
饿汉式:在类加载的时候已经创建好该单例对象
- 静态常量
- 静态代码块
- 双重检查
- 静态内部类
- 枚举
-
懒汉式:在需要使用对象的时候才会去创建对象
- 线程不安全
- 线程安全,同步方法
- 线程安全,同步代码块
饿汉式(静态常量)
package com.learning.principle.singleton._01HungrySingle;
import java.sql.SQLOutput;
/**
* @author nieshenglei
*/
public class SingletonClient {
//测试
public static void main(String[] args) {
HungrySingleton instance1 = HungrySingleton.getInstance();
HungrySingleton instance2 = HungrySingleton.getInstance();
System.out.println("instance1==instance2 ---> "+(instance1==instance2));
System.out.println("instance1.hashCode() "+instance1.hashCode());
System.out.println("instance2.hashCode() "+instance2.hashCode());
}
}
/**
*
* 饿汉式单例模式
* 优点:加载时就创建单例,以后就不用改变.线程是安全的,可以直接用于多线程而不会出现问题
* 缺点:在类装载的时候就完成了实例化,没有达到懒加载的效果,如果从始至终就没有使用过这个实例,则会造成内存浪费
*
* @author nieshenglei
*/
class HungrySingleton {
/**
* final 防止反射破坏单例
*/
private static final HungrySingleton INSTANCE = new HungrySingleton();
/**
* 构造器私有化
*/
private HungrySingleton(){}
/**
* 获取示例方法
* @return HungrySingleton
*/
public static HungrySingleton getInstance(){
return INSTANCE;
}
}
运行结果
instance1==instance2 ---> true
instance1.hashCode() 460141958
instance2.hashCode() 460141958
饿汉式(静态代码块)
package com.learning.principle.singleton._02HungrySingle;
/**
* @author nieshenglei
*/
public class SingletonClient {
//测试
public static void main(String[] args) {
HungrySingleton instance1 = HungrySingleton.getInstance();
HungrySingleton instance2 = HungrySingleton.getInstance();
System.out.println("instance1==instance2 ---> "+(instance1==instance2));
System.out.println("instance1.hashCode() "+instance1.hashCode());
System.out.println("instance2.hashCode() "+instance2.hashCode());
}
}
/**
*
* 饿汉式单例模式 静态代码块
* 优点:加载时就创建单例,以后就不用改变.线程是安全的,可以直接用于多线程而不会出现问题
* 缺点:在类装载的时候就完成了实例化,没有达到懒加载的效果,如果从始至终就没有使用过这个实例,则会造成内存浪费
*
* @author nieshenglei
*/
class HungrySingleton {
/**
* final 防止反射破坏单例
*/
private static final HungrySingleton INSTANCE ;
/**
* 静态代码块 生成实例
*/
static {
INSTANCE = new HungrySingleton();
}
/**
* 构造器私有化
*/
private HungrySingleton(){}
/**
* 获取示例方法
* @return HungrySingleton
*/
public static HungrySingleton getInstance(){
return INSTANCE;
}
}
运行结果
instance1==instance2 ---> true
instance1.hashCode() 460141958
instance2.hashCode() 460141958
懒汉式(简单懒汉 线程不安全)
package com.learning.principle.singleton._03LazySingle;
/**
* @author nieshenglei
*/
public class SingletonClient {
//测试
public static void main(String[] args) {
System.out.println("懒汉式单例 -- 线程不安全");
LazySingleton instance1 = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
System.out.println("instance1==instance2 ---> "+(instance1==instance2));
System.out.println("instance1.hashCode() "+instance1.hashCode());
System.out.println("instance2.hashCode() "+instance2.hashCode());
}
}
/**
*
* 懒汉式单例模式
* 线程不安全 只能在单线程下运行
*
* 如果在多线程,一个线程进入 getInstance() 方法中的if判断,但未执行,另外一个线程通过的if判断,容易产生多实例
*
* @author nieshenglei
*/
class LazySingleton {
/**
* 构造器私有化
*/
private LazySingleton(){}
/**
* final 防止反射破坏单例
*/
private static LazySingleton INSTANCE ;
/**
* 静态代码块 生成实例
*/
static {
INSTANCE = new LazySingleton();
}
/**
* 获取示例方法 当调用该方法的时候才去创建对象
* @return LazySingleton
*/
public static LazySingleton getInstance(){
if (INSTANCE == null) {
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
懒汉式(线程安全)
package com.learning.singleton._04LazySingle;
/**
* @author nieshenglei
*/
public class SingletonClient {
//测试
public static void main(String[] args) {
System.out.println("懒汉式单例 -- 线程安全");
LazySingleton instance1 = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
System.out.println("instance1==instance2 ---> "+(instance1==instance2));
System.out.println("instance1.hashCode() "+instance1.hashCode());
System.out.println("instance2.hashCode() "+instance2.hashCode());
}
}
/**
*
* 懒汉式单例模式
* 线程安全
*
* 在 getInstance() 方法上使用 synchronized 关键字
*
* 存在问题: synchronized 是重量级锁 效率低 在多线程情况下 所有执行getInstance()方法的的线程 都要排队 等前一线程执行完毕后才能执行
*
* @author nieshenglei
*/
class LazySingleton {
/**
* 构造器私有化
*/
private LazySingleton(){}
/**
* final 防止反射破坏单例
*/
private static LazySingleton INSTANCE ;
/**
* 静态代码块 生成实例
*/
static {
INSTANCE = new LazySingleton();
}
/**
* 获取示例方法 当调用该方法的时候才去创建对象
*
* synchronized 关键字 当有一个线程1去执行getInstance() 方法的时候,其他线程执行需要排队,等线程1执行完成才会执行
*
* @return LazySingleton
*/
public static synchronized LazySingleton getInstance(){
if (INSTANCE == null) {
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
懒汉式(双重校验锁)
package com.learning.singleton._05LazySingle;
/**
* @author nieshenglei
*/
public class SingletonClient {
//测试
public static void main(String[] args) {
System.out.println("懒汉式单例 -- 线程安全");
LazySingletonDoubleCheck instance1 = LazySingletonDoubleCheck.getInstance();
LazySingletonDoubleCheck instance2 = LazySingletonDoubleCheck.getInstance();
System.out.println("instance1==instance2 ---> " + (instance1 == instance2));
System.out.println("instance1.hashCode() " + instance1.hashCode());
System.out.println("instance2.hashCode() " + instance2.hashCode());
}
}
/**
* 懒汉式单例模式 双重检查
*
* @author nieshenglei
*/
class LazySingletonDoubleCheck {
/**
* 构造器私有化
*/
private LazySingletonDoubleCheck() {
}
/**
* 添加 volatile 关键字 保证 instance 在所有线程中同步
*/
private static volatile LazySingletonDoubleCheck INSTANCE;
/**
* 获取示例方法 当调用该方法的时候才去创建对象
* <p>
* synchronized 关键字 + 两次if判断 保证线程安全; 当多个线程同时进入到外层if判断后,获取到锁后,再次检查是否为空若为空着则创建,否则不创建
*
* @return LazySingletonDoubleCheck
*/
public static LazySingletonDoubleCheck getInstance() {
if (INSTANCE == null) {
synchronized (LazySingletonDoubleCheck.class) {
if (INSTANCE == null) {
INSTANCE = new LazySingletonDoubleCheck();
}
}
}
return INSTANCE;
}
}
懒汉式(静态内部类)
package com.learning.singleton._06LazySingle;
/**
* @author nieshenglei
*/
public class SingletonClient {
//测试
public static void main(String[] args) {
System.out.println("懒汉式单例 -- 线程安全");
HungrySingleton instance1 = HungrySingleton.getInstance();
HungrySingleton instance2 = HungrySingleton.getInstance();
System.out.println("instance1==instance2 ---> " + (instance1 == instance2));
System.out.println("instance1.hashCode() " + instance1.hashCode());
System.out.println("instance2.hashCode() " + instance2.hashCode());
}
}
/**
* 懒汉式单例模式 静态内部类
*
* 这种方式采用了类装载的机制来保证初始化实例时之后又一个线程
*
* 类的静态属性只会在第一次加载类的时候初始化,所以这里,jvm帮助我们保证线程的安全性,在类进行初始化时,别的线程是无法进入的
*
* @author nieshenglei
*/
class HungrySingleton {
/**
* 构造器私有化
*/
private HungrySingleton() {
}
/**
* 添加 volatile 关键字 保证 instance 在所有线程中同步
*/
private static volatile HungrySingleton INSTANCE;
/**
* 静态内部类 SingletonInstance 当 HungrySingleton 装载时,并不会被实例化,而是在需要实例化时,调用getInstance方法,才会装载singleInstance类,
* 从而完成 HungrySingleton实例化
*/
private static class SingletonInstance {
private static final HungrySingleton INSTANCE = new HungrySingleton();
}
/**
* 获取示例方法 当调用该方法的时候才去创建对象
* <p>
*
* @return HungrySingleton
*/
public static HungrySingleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
懒汉式(枚举)
package com.learning.singleton._07LazySingle;
/**
* @author nieshenglei
*/
public class SingletonClient {
//测试
public static void main(String[] args) {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1==instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
instance1.sayOk();
}
}
/**
* 使用枚举,可以实现单例
*
* 借助JDK1.5中添加的枚举类来实现单例模式,不仅能避免多线程同步为题,而且还能防止反序列化重新创建新的对象
*
* @author nieshenglei
*/
enum EnumSingleton {
/**
*
*/
INSTANCE;
public void sayOk() {
System.out.println("~Ok!");
}
}
扩展
反射下单例的问题(一)
/**
* 无论是懒汉式还是饿汉式 ,其实我们都可以通过反射强制访问类的私有构造器去破坏
* 基于 双重检验锁
* @author nieshenglei
*/
public class LazySingletonProxy {
private volatile static LazySingletonProxy INSTANCE;
private LazySingletonProxy(){
synchronized (LazySingletonProxy.class){
if(INSTANCE != null){
throw new RuntimeException("禁止使用反射去破坏单例");
}
}
}
public static LazySingletonProxy getInstance(){
if(INSTANCE == null){
synchronized (LazySingletonProxy.class){
if(INSTANCE == null){
INSTANCE = new LazySingletonProxy();
}
}
}
return INSTANCE;
}
}
反射下单例的问题(二)
/**
* 无论是懒汉式还是饿汉式 ,其实我们都可以通过反射强制访问类的私有构造器去破坏
* 虽然加上锁 但是单例还是会被反射破坏 可以通过加入flag标志 防止多次反射破坏单例
* 但是反射又可以通过读取 flag 字段.并强制设置flag的属性值
*
* 反射不可以配置枚举类
*
* @author nieshenglei
*/
public class LazySingletonProxyFlag {
private volatile static LazySingletonProxyFlag INSTANCE;
private static Boolean flag = false;
private LazySingletonProxyFlag(){
synchronized (LazySingletonProxyFlag.class){
if(!flag){
flag = true;
}else {
throw new RuntimeException("禁止使用反射去破坏单例");
}
}
}
public static LazySingletonProxyFlag getInstance(){
if(INSTANCE == null){
synchronized (LazySingletonProxyFlag.class){
if(INSTANCE == null){
INSTANCE = new LazySingletonProxyFlag();
}
}
}
return INSTANCE;
}
}