前言
简述:以下为单例模式的使用,单例模式的使用方法有很多,可以根据项目自身情况进行选择,选择的设计模式适合自己的就是好的设计模式
一、饿汉式单例模式
/**
* 饿汉式单例模式
* </p>
* 优点:简单方便,不用担心线程安全问题,保证对象只存在一个单例
* 缺点:不管程序中是否使用该对象,都会产生实例对象,会降低应用的启动速度
*/
public class HungrySingleton {
private static HungrySingleton singleton = new HungrySingleton();
//构造方法私有化,确保不能被外部调用
private HungrySingleton() {
}
//获取对象的唯一入口
public static HungrySingleton getInstance() {
return singleton;
}
public void println(Object o) {
System.out.println("o = " + o);
}
}
程序在加载的时候,对象就会变初始化,这也就是我们俗称的饿汉式
单例化主要是为了确保程序中改对象只有一个实例,检测代码如下
public class Main {
public static void main(String[] args) {
//检测对象是否唯一
HungrySingleton s1 = HungrySingleton.getInstance();
HungrySingleton s2 = HungrySingleton.getInstance();
System.out.println("对象是否相等:" + (s1 == s2));
}
}
输出结果:
对象是否相等:true
结论:对象只有一个
二、懒汉式单例
/**
* 懒汉式单例化
* </p>
* 优点:对象的生成是在应用使用的时候才会去构造,可以提高应用的启动速度
* 缺点:不是线程安全的,如果多个线程同时调用getInstance()方法,可能会产生多个单例对象
*/
public class LazySingleton {
private static LazySingleton singleton = null;
//构造方法私有化,确保不能被外部调用
private LazySingleton() {
}
//获取对象的唯一入口
public static LazySingleton getInstance() {
if (singleton == null) {
//此处模拟对象实例化要消耗的时间
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new LazySingleton();
}
return singleton;
}
public void println(Object o) {
System.out.println("o = " + o);
}
}
懒汉式单例不用随着程序启动就实例化,而是需要的时候调用getInstance()方法才会实例化,但是在多线程使用的情况下,无法保证实例唯一性
检测代码如下:
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//通过Thread创建10个线程
//直接输出对象地址
System.out.println("对象地址 = " + LazySingleton.getInstance().toString());
}).start();
}
}
}
输出结果:
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@6e4b852a
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7079d68d
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@632a10ea
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@54437fbd
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@72679c71
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@79a2688b
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@6e4b852a
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@8ae2e7e
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@3354d998
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@48ab8b1a
结论:内存地址输出的结果各式各样,说明对象无法在多线程的情况下无法保证唯一性
三、懒汉式加锁单例
/**
* 懒汉式加锁单例化
* </p>
* 优点:保证对象创建的时候不会多次实例化
* 缺点:由于同步锁的原因,会导致获取对象的时候每次都要检查锁的状态,消耗性能
*/
public class LazySingleton {
private static LazySingleton singleton = null;
//构造方法私有化,确保不能被外部调用
private LazySingleton() {
}
//获取对象的唯一入口
public static synchronized LazySingleton getInstance() {
if (singleton == null) {
//此处模拟对象实例化要消耗的时间
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new LazySingleton();
}
return singleton;
}
public void println(Object o) {
System.out.println("o = " + o);
}
}
懒汉式加锁单例可以保证唯一性,但是会浪费过多性能,每次获取对象的等待时间可能会延长
检测代码如下:
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//通过Thread创建10个线程
//直接输出对象地址
System.out.println("对象地址 = " + LazySingleton.getInstance().toString());
}).start();
}
}
}
输出结果:
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
结论:内存地都一样,说明对象是唯一的
四、懒汉式双重锁单例
/**
* 懒汉式双重锁单例化
* </p>
* 优点:减少性能的浪费,需要的时候才进行实例化
* 缺点:代码复杂了一点
*/
public class LazySingleton {
//关于volatile关键字可以百度查看相关介绍
private static volatile LazySingleton singleton = null;
//构造方法私有化,确保不能被外部调用
private LazySingleton() {
}
//获取对象的唯一入口
public static LazySingleton getInstance() {
//此处检查必须保留,负责和方法上加锁是一样的道理
if (singleton == null) {
synchronized (LazySingleton.class) {
if (singleton == null) {
singleton = new LazySingleton();
}
}
}
return singleton;
}
public void println(Object o) {
System.out.println("o = " + o);
}
}
五、静态内部类单例化
/**
* 内部静态类单例化
* </p>
* 优点:线程安全
* 缺点:可能会被人为破坏,静态内部类如果添加多个单例对象,那只要有一个使用,其他的都会被初始化
*/
public class InsideSingleton {
//构造方法私有化,确保不能被外部调用
private InsideSingleton() {
}
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
*/
private static class SingletonHolder {
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static final InsideSingleton INSTANCE = new InsideSingleton();
}
public static InsideSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这里就不演示了,需要注意上面说的缺点即可
六、枚举单例化
/**
* 枚举单例化
* </p>
* 优点:唯一,线程安全,可以拒绝被反射
* 缺点:暂时还没有
*/
public enum EnumSingleton {
INSTANCE;
public void println(Object o) {
System.out.println(o);
}
}
使用如下:
public class Main {
public static void main(String[] args) {
EnumSingleton.INSTANCE.println("hha");
}
}
使用结语
以上列举了多种单例模式的写法,分析了其利弊之处。同时还介绍了目前最佳的单例写法——枚举模式(枚举单例思路主要来源于:Effective Java 这本书),单例模式介绍就到这了,如文中发现有误之处还希望大家指出,谢谢!