单例模式 (创建型模式)
什么是单例模式
在JVM运行环境中,一个类只存在一个实例对象,并且这个类只提供一个获取到该实例的方法(静态方法)
为什么要使用单例模式
频繁使用的对象实例,且对象本身的属性不改变,每次都 new 一个实例,且执行实例方法结果都一样,这样就会存在多个实例对象,造成系统资源的浪费。
单例模式八种写法
- 饿汉式<线程安全> (当确保当前实例一定会使用一次或多次的时候;推荐使用)
- 饿汉式<线程安全,静态代码块>(推荐使用)
- 懒汉式<线程不安全>(不推荐使用)
- 懒汉式<线程安全;同步方法>(不推荐使用)
- 懒汉式<线程不安全,同步代码快>(不推荐使用)
- 懒汉式<线程安全,同步代码快,双重检查>(推荐使用)
- 静态内部类<线程安全>(推荐使用)
- 枚举<线程安全>(推荐使用)
1:饿汉式<线程安全>
/**
* 饿汉式 最简单的写法
*/
public class Singleton01 {
/**
* 私有化构造函数,防止外部可以 new
*/
private Singleton01() {}
/**
* 声明静态变量,且实例化
*/
private final static Singleton01 SINGLETON = new Singleton01();
/**
* 获取单例对象
* @return {@link #SINGLETON}
*/
public static Singleton01 getInstance() {
return SINGLETON;
}
}
优缺点说明
优点:写法简单,在类装载的时候,就创建了对象实例,避免线程同步问题;
缺点: 在类装载的时候,就实例化了对象,没有达到懒加载的效果;如果对象实例一直没有被使用,会占据内存资源;
2:饿汉式<线程安全;静态代码快>
/**
* 饿汉式 静态代码块的写法
*/
public class Singleton02 {
/**
* 私有化构造函数,防止外部可以 new
*/
private Singleton02() {}
/**
* 声明静态变量,且实例化
*/
private static Singleton02 SINGLETON;
/**
* 静态代码快,类装载就会执行
*/
static {
SINGLETON = new Singleton02();
}
/**
* 获取单例对象
* @return {@link #SINGLETON}
*/
public static Singleton02 getInstance() {
return SINGLETON;
}
}
优缺点说明
优点:写法简单,在类装载的时候,就会执行静态代码块创建对象实例,避免线程同步问题;
缺点: 在类装载的时候,就实例化了对象,没有达到懒加载的效果;如果对象实例一直没有被使用,会占据内存资源;
3:懒汉式<线程不安全>
/**
* 懒汉式 线程不安全
*/
public class Singleton03 {
/**
* 私有化构造函数,防止外部可以 new
*/
private Singleton03() {}
/**
* 声明静态变量
*/
private static Singleton03 singleton03;
/**
* 获取单例对象
* @return {@link #singleton03}
*/
private static Singleton03 getInstance() {
if (singleton03 == null) {
/**
* 如果 {@link #singleton03} 为 null 的情况,就实例化
* 但是在多线程的情况下,会同时进入到这里,对{@link #singleton03}进行实例化
*/
singleton03 = new Singleton03();
}
return singleton03;
}
}
优缺点说明
优点: 起到了懒加载的作用,但只能适用于单线程。
缺点: 在多线程情况下,一个线程进入 if (singleton03 == null)判断语句快,还未来得及实例化对象;另一个线程也进来了,这就会产生多个实例对象;
4:懒汉式<线程安全;同步方法>
/**
* 懒汉式 线程安全 同步方法
**/
public class Singleton04 {
/**
* 私有化构造函数,防止外部可以 new
*/
private Singleton04() {}
/**
* 声明静态变量
*/
private static Singleton04 singleton04;
/**
* 方法加上 synchronized 同步关键字,每次访问都会加锁,保证线程安全
* 但是如果 {@link #singleton04} 已经实例化了,加锁就会影响性能
* @return {@link #singleton04}
*/
public synchronized static Singleton04 getInstance() {
if (singleton04 == null) {
singleton04 = new Singleton04();
}
return singleton04;
}
}
优缺点说明
优点: 起到了懒加载的作用,解决了线程安全问题
缺点: 多线程情况下,每个线程要获取对象实例,都需要加锁,而对象实例初始化只需要一次就可以,会导致效率变低
5:懒汉式<线程不安全,同步代码快>
/**
* 懒汉式 线程不安全,同步代码快
**/
public class Singleton05 {
/**
* 私有化构造函数,防止外部可以 new
*/
private Singleton05() {}
/**
* 声明静态变量
*/
private static Singleton05 singleton05;
/**
* 获取实例对象
* @return {@link #singleton05}
*/
public static Singleton05 getInstance() {
if (singleton05 == null) {
/**
* 如果 {@link #singleton05} 为空,就获取锁,执行实例化操作
* 但可能会有多个线程同时进入到这里,而进行多次实例化,
*/
synchronized (Singleton05.class) {
singleton05 = new Singleton05();
}
}
return singleton05;
}
}
优缺点说明
优点: 毫无优点
缺点: 多线程的情况下,会同时进入到 if (singleton05 == null) 方法快中,虽然有锁的代码快,但是线程进来后同样会实例化对象;
6:懒汉式<线程安全,同步代码快,双重检查>
/**
* 懒汉式<线程安全,同步代码快,双重检查>
**/
public class Singleton06 {
/**
* 私有化构造函数,防止外部可以 new
*/
private Singleton06() {}
/**
* 声明静态变量
*/
private static Singleton06 singleton06;
/**
* 获取对象实例
* @return
*/
public static Singleton06 getSingleton() {
if (singleton06 == null) {
synchronized (Singleton06.class) {
/**
* 再加一个判断,防止多线程进入,而造成多次实例化对象
*/
if (singleton06 == null) {
singleton06 = new Singleton06();
}
}
}
return singleton06;
}
}
优缺点说明
优点: 延迟加载,线程安全,效率较高;在实际开发中,推荐使用这种写法
值得一提的是:多重检查在多线程开发中经常使用。
7:静态内部类<线程安全>
/**
* 静态内部类<线程安全>
**/
public class Singleton07 {
/**
* 私有化构造函数,防止外部可以 new
*/
private Singleton07() { }
/**
* 获取实例
* @return
*/
public static Singleton07 getInstance() {
return Singleton07Instance.SINGLETON07;
}
/**
* 静态内部类,
* 在外部类{@link Singleton07}装载时,并不会装载该类{@link Singleton07Instance},
* 而是在需要实例化的时,调用{@link #getInstance()}才会装载{@link Singleton07Instance}类,
* 从而完成 {@link #SINGLETON07} 的实例化
*/
private static class Singleton07Instance {
/* 声明一个静态的实例对象 */
final static Singleton07 SINGLETON07 = new Singleton07();
}
}
优缺点说明
优点: 静态内部类方式不会在外部类装载的时候而装载,而是等调用该类的方法时,才会进行装载;JVM只会装载一次类,所以是线程安全的,也实现了懒加载;
8:枚举<线程安全>
/**
* 枚举单例模式
**/
public enum Singleton08 {
/**
* 只声明一个一个属性
*/
SINGLETON;
/**
* Singleton08.SINGLETON.print(); 调用单例方法
*/
public void print() {
System.out.println("这是枚举单例");
}
}
优缺点说明
优点: 避免多线程同步问题,还能防止反序列化创建对象;
值得提一下:反射可以破坏单例唯一实例
单例类
反射调用单例的构造方法测试,会重新创建一个实例对象;
避免利用反射,而破坏单例,在构造方法里改造下;
先用反射创建实例,就不会报错了;
在实际开发中,不要用反射去创建单例的实例对象,很显然违背了单例模式的唯一实例原则;
觉得对您有帮助,就点个赞呗。😀