一、前言:
- 单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证并发环境下中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。
- 常见的单例模式:
饿汉模式: 在程序启动时即创建对象实例。
懒汉模式:仅当程序中使用到改对象时,才回去创建对象。
二、单例模式实例:
1. 饿汉模式,程序启动,对象实例被创建 【不推荐】:
/**
* @Des: 饿汉模式
*/
public class SingleTest01 {
// 静态变量系统启动就会被加载
private static SingleTest01 singleTest = new SingleTest01();
// 私有化构造方方
private SingleTest01(){
System.out.println(" init ");
}
// 返回对象
public static SingleTest01 newSingleTest(){
return singleTest;
}
}
优点:
- 代码实现简单,利用类加载机制保证线程安全。
缺点:
- 在程序启动时,就已经完成实例化,如果对象没有使用,会造成内存浪费。
- 如果对象在启动时存在一些耗时操作,会影响到我们程序启动时间
2、 懒汉模式, 当程序用到该实例时去创建对象,使用 synchronized 对获取方法进行加锁,实现并发安全【不推荐】:
/**
* @Des: 懒汉模式
*/
public class SingleTest02 {
// 静态变量保存对象
private static SingleTest02 singleTest = null;
// 私有化构造方法
private SingleTest02(){
System.out.println(" init ");
}
// synchronized 修饰方法保证并发安全
public static synchronized SingleTest02 newSingleTest() {
if (singleTest == null){
singleTest = new SingleTest02();
}
return singleTest;
}
}
优点:
- 就是饿汉模式的缺点
缺点:
- 效率太低,加锁的细粒度太大,其实仅仅在第一次创建对象时需要加锁,实例化完成之后,获取的时候完全没有必要加锁。
三、单例模式 – 懒汉模式优化:
第一种优化方案,缩小锁粒度:
- 使用 volatile 保证线程可见性
- 对象为被实例化时,通过代码快进行加锁,双重检验保证最终结果单例。
/**
* @Des: 懒汉模式
*/
public class SingleTest03 {
// 静态变量保存对象, volatile 保证每个线程读取最新的数据
private static volatile SingleTest03 singleTest = null;
// 私有化构造方法
private SingleTest03(){
System.out.println(" init ");
}
// 获取实例对象
public static SingleTest03 newSingleTest() {
if (singleTest == null){
// singleTest 为空,表示没有初始化,将当前类加锁。
synchronized(SingleTest03.class){
// 双重校验,避免第一个if之后有多个线程在等待。
if (singleTest == null){
singleTest = new SingleTest03();
}
}
}
return singleTest;
}
}
第二种优化方案,使用静态内部类:
- 利用类加载机制,保证实例化对象时仅有一个线程,内部类仅在使用时会初始化静态属性,实现了懒加载,效率高, 和第一中优化方案差不多,代码如下:
/**
* @Des: 懒汉模式
*/
public class SingleTest04 {
private static class SingletonInstance{
private final static SingleTest04 SINGLETON = new SingleTest04();
}
private SingleTest04(){
System.out.println(" init ");
}
// 获取实例对象
public static SingleTest04 newSingleTest() {
return SingletonInstance.SINGLETON;
}
}
四、目前推荐的单例的创建方式:
- 上面的方式虽然都实现了单例模式,各有各自的优缺点。但是他们都有一个公共的 缺点,无法防止暴力创建对象,例如: 反射、序列化、克隆。
- 在 《Effective Java》作者的Josh Bloch提倡我们使用枚举的方式来创建单例对象,使用非常简单。
关于枚举的博文: https://blog.csdn.net/zhangyong01245/article/details/103322007 - 代码示例:
/**
* @Des: 枚举实现单例
*/
public enum Singleton {
// Singleton的单例对象,枚举被加载时,由JVM创建,线程安全
INSTANCE;
// 单例对象中的方法
public void print(){
System.out.println(this.hashCode());
}
}
测试类:
public class Test {
public static void main(String[] args) {
for (int i =0; i<10;i++){
new Thread(new Runnable() {
public void run() {
Singleton singleton = Singleton.INSTANCE;
singleton.print();
}
}).start();
}
}
}
打印结果: