一、什么是单例
单例模式指的是保证在一个jvm中,一个类只有一个实例,并提供一个全局访问点。通过私有构造函数,提供唯一获取对象实例方法(getInstance())。
优点:共享资源,节省创建时间,提高性能。
缺点:可能存在线程不安全问题。
二、实现单例的方式
1> 饿汉式
public class SingletonV1 {
/**
* 饿汉式
* 优点:先天性线程是安全的,当类初始化的 就会创建该对象。
* 缺点:如果饿汉式使用过多,可能会影响项目启动的效率问题。
*/
private static SingletonV1 singletonV1 = new SingletonV1();
/**
* 将构造函数私有化 禁止初始化
*/
private SingletonV1() {
}
/**
* 提供外部访问的获取实例方法
*/
public static SingletonV1 getInstance() {
return singletonV1;
}
public static void main(String[] args) {
SingletonV1 instance1 = SingletonV1.getInstance();
SingletonV1 instance2 = SingletonV1.getInstance();
System.out.println(instance1 == instance2);
}
}
2> 懒汉式
public class SingletonV2 {
/**
* 解决了饿汉式的效率问题,但是会存在线程安全问题
* 懒汉式
*/
private static SingletonV2 singletonV2;
private SingletonV2() {
}
/**
* 在真正需要创建对象的时候使用...
*
* @return
*/
public static SingletonV2 getInstance() {
if (singletonV2 == null) {
try {
Thread.sleep(2000);
} catch (Exception e) {
}
singletonV2 = new SingletonV2();
}
return singletonV2;
}
public static void main(String[] args) {
// 1.模拟线程不安全
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
public void run() {
SingletonV2 instance1 = SingletonV2.getInstance();
System.out.println(Thread.currentThread().getName() + "," + instance1);
}
}).start();
}
}
}
3> 懒汉式(线程安全)
public class SingletonV3 {
/**
* 懒汉式 线程安全
*/
private static SingletonV3 singletonV3;
private SingletonV3() {
}
/**
* 能够解决线程安全问题,创建和获取实例时都上锁 ,效率非常低,所以推荐使用双重检验锁
*
* @return
*/
public synchronized static SingletonV3 getInstance() {
try {
Thread.sleep(2000);
} catch (Exception e) {
}
if (singletonV3 == null) {
System.out.println("创建实例SingletonV3");
singletonV3 = new SingletonV3();
}
System.out.println("获取SingletonV3实例");
return singletonV3;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
public void run() {
SingletonV3 instance1 = SingletonV3.getInstance();
System.out.println(Thread.currentThread().getName() + "," + instance1);
}
}).start();
}
}
}
4> 双重检验锁(DCL)
public class SingletonV4 {
/**
* volatile 禁止重排序和 提高可见性,在懒汉式的基础上,提高了获取对象的效率
*/
private volatile static SingletonV4 singletonV4;
private SingletonV4() {
}
public static SingletonV4 getInstance() {
if (singletonV4 == null) { // 第一次判断如果没有创建对象 开始上锁...
synchronized (SingletonV4.class) {
if (singletonV4 == null) { // 当用户抢到锁,判断初始化
System.out.println("第一次开始创建实例对象....获取锁...");
try {
Thread.sleep(2000);
} catch (Exception e) {
}
singletonV4 = new SingletonV4();
}
}
}
return singletonV4;//在实例化完成后,进行获取对象时没有加锁
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
public void run() {
SingletonV4 instance1 = SingletonV4.getInstance();
System.out.println(Thread.currentThread().getName() + "," + instance1);
}
}).start();
}
}
}
5> 静态内部类
public class SingletonV5 {
private SingletonV5() {
System.out.println("对象初始...");
}
public static SingletonV5 getInstance() {
return SingletonV5Utils.singletonV5;
}
/**
* 静态内部方式能够避免同步带来的效率问题 并且能实现延迟加载
*/
public static class SingletonV5Utils {
private static SingletonV5 singletonV5 = new SingletonV5();
}
public static void main(String[] args) {
System.out.println("项目启动成功");
SingletonV5 instance1 = SingletonV5.getInstance();
SingletonV5 instance2 = SingletonV5.getInstance();
System.out.println(instance1 == instance2);
}
}
6> 枚举
public enum EnumSingleton {
INSTANCE;
// 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破解
public void add() {
System.out.println("add方法...");
}
}
public static void main(String[] args) throws Exception {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1 == instance2);
//即使使用暴力破解,依旧会报错,找不到枚举的无参构造方法
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingleton v3 = declaredConstructor.newInstance();
System.out.println(v3==instance1);
}
总结: 对于以上的6种单例的实现,可以实现线程安全,但许多仍可以通过反射及序列换破解。在不改造jvm的情况下,枚举是最为安全的单例实现。 原因就在于 反射的newInstance()方法对枚=枚举类进行了判断,如下图所示:
参考来自: 蚂蚁课堂