含义:单例模式,顾名思义就是只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
实现单例模式的四种种方式
1、饿汉式:
/**
* 饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全
* 简单实用,推荐使用!
* 缺点:不管用到与否,类装载时就完成实例化
* 相比于枚举单例,不能防止序列化和反序列化攻击
*/
public class MgrSingleton {
private static final MgrSingleton INSTANCE = new MgrSingleton ();
private MgrSingleton () {};
public static MgrSingleton getInstance() {
return INSTANCE;
}
public void m() {
//业务代码
}
public static void main(String[] args) {
MgrSingleton m1 = MgrSingleton.getInstance();
MgrSingleton m2 = MgrSingleton.getInstance();
System.out.println(m1 == m2);
}
}
2、懒汉式:
/**
* lazy loading
* 也称懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
* 可以通过synchronized解决,但也带来效率下降
*/
public class MgrSingleton {
private static volatile MgrSingleton INSTANCE; //JIT
private MgrSingleton () {
}
public static MgrSingleton getInstance() {
if (INSTANCE == null) {
//双重检查
synchronized (MgrSingleton.class) {
if(INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new MgrSingleton ();
}
}
}
return INSTANCE;
}
public void m() {
//业务逻辑
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(MgrSingleton.getInstance().hashCode());
}).start();
}
}
}
说明:为什么需要双重判定
synchronized 内if判断的原因,当多个线程来到锁外时,每个线程都会逐步获得锁并且创建新对象,加if避免
synchronized 外if判断的原因,可以不要,但是锁是一个影响效率的操作,使用逻辑判断减少锁操作,增加效率,理论上说,当对象创建完成,外if可以将所有的创建抵挡在外而直接返回
3、静态内部类
/**
* 静态内部类方式
* JVM保证单例
* 加载外部类时不会加载内部类,这样可以实现懒加载
*/
public class MgrSingleton {
private MgrSingleton() {
}
private static class MgrSingletonHolder {
private final static MgrSingleton INSTANCE = new MgrSingleton();
}
public static MgrSingleton getInstance() {
return MgrSingletonHolder.INSTANCE;
}
public void m() {
//业务逻辑
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(MgrSingleton.getInstance().hashCode());
}).start();
}
}
}
4、枚举类型:
/**
* 不仅可以解决线程同步,还可以防止反序列化。
*/
public enum MgrSingleton {
INSTANCE;
public void m() {
//业务逻辑
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(MgrSingleton.INSTANCE.hashCode());
}).start();
}
}
}
其中枚举单例来自effective java,该方法将会成为最好的单例模式。为什么这样说呢,因为它符合线程安全和延迟加载,并且可以有效防御两种破坏单例(即单例产生多个实例)的行为:反射攻击与序列化攻击
例如:
非枚举类型通过反射的方式获取的对象与之前的对象作比较为false
枚举类型是如何做到这一点的
有一篇博客分析的很全面:枚举类型如何避免反射攻击