单例模式是什么?
单例模式:某个类只能有一个实例,提供一个全局的访问点。
不管什么时候,都要确保某一个类只有一个实例,而且是自行实例化并向整个系统提供这个实例,这样的一个类称为单例类,它提供全局访问的方法。
单例模式是一种对象创建型模式。
单例模式为什么存在?
单例模式存在的意义是什么?简单来说就是单例模式的需求场景是哪些?
其实单实例模式在我们平常的使用中是很常见的,比如我们经常会调用某个数据的连接池。
那个就是单例模式,为了保证整个系统中只有一个连接池,所有的人访问同一个连接池。
为什么需要保证系统里面只有一个这样的对象呢?
主要为了节约资源,就拿我们数据库连接池来说,你一个连接池管理50个链接和两个连接池分别管理25个链接,其实使用的复杂度是不一样的,如果是两个,如果有个被占满了,你就选择那个没有占满的;同时你不知道啥时候会出现占满的情况,这就导致你每次用连接池的时候都要进行判断,这样操作无疑就增加了复杂度,而且没啥必要。而且系统生成两个对象还造成了不必要的资源浪费,堆栈都要开辟空间,这些都是没啥必要的资源浪费。
还有日志,这个就是典型的单例了,每个java类中,只要保证只要对应本
意义:减少内存开销,节约系统资源。
单例模式如何实现?
(一)饿汉思想
这个可以保证只有一个实例,因为在类初始化的时候就创建了实例了。
缺点: 堆内存中驻留很长时间,但是却不一定会被使用。
/**
* hungry mode
* @author wanghanwei.wb
*/
public final class SingletonHungry {
private byte[] date = new byte[1024];
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry() {
}
public static SingletonHungry getInstance() {
return instance;
}
}
(二)懒汉思想
优点: 在需要的时候才被实例化
缺点: 存在线程安全的问题,
public final class SingletonLazy {
private byte[] date = new byte[1024];
private static SingletonLazy instance = null;
private SingletonLazy() {
}
public SingletonLazy getInsatnce() {
if(instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
(三)懒汉思想+同步方法
优点: 没有了并发的问题
缺点: 每次只能有一个线程获取getInstance()方法的使用权,别的线程同时访问的时候只能等待,性能不佳。
public final class SingletonSync {
private byte[] data = new byte[1024];
private static SingletonSync instance = null;
private SingletonSync() {
}
public static synchronized SingletonSync getInstance() {
if (instance == null) {
instance = new SingletonSync();
}
return instance;
}
}
(四)double check 实现方式
优点: 解决高并发的问题的同时,也解决了每次只能有一个线程访问的难题
缺点: 因为CPU的指令重排的,依然存在风险。
/**
* double check
* @author wanghanwei.wb
*
*/
public class SingletonDoubleCheck {
private byte[] data = new byte[1024];
private static SingletonDoubleCheck instance = null;
private SingletonDoubleCheck() {
}
public static SingletonDoubleCheck getInstance() {
// first check
if (instance == null) {
// 要保证所有的线程用的是同一个 单例对象,所以需要锁了class对象。
synchronized (SingletonDoubleCheck.class) {
// second check
if (instance == null) {
instance = new SingletonDoubleCheck();
}
}
}
return instance;
}
}
(五)double check 安全实现方式
double check的潜在危险:因为cpu的指令重排导致的问题:比如说instance已经不是null了,但是依然没有执行实例化的代码,所以获取conn和socket依然为null,使用者会有空值异常。
这么说你可能会迷糊,但是这个重要的就是new的过程:
这里引用《极客时间-Java并发编程实战-01 | 可见性、原子性和有序性问题:并发编程Bug的源头》
我们以为的 new 操作应该是:
在堆内存中分配一块内存 M;
在内存 M 上初始化 Singleton 对象;
然后 M 的地址赋值给 栈内存上的instance 变量。
但是实际上优化后的执行路径却是这样的:
在堆内存中分配一块内存 M;
将 M 的地址赋值给 栈内存上的 instance 变量;
最后在内存 M 上初始化 Singleton 对象。
所以引用instance不为空的时候,实际上引用对应的堆内存依然没有null,但是别的线程判断instance == null的时候,就已经不会判断为null了,就会让别的线程执行别的逻辑。
错误代码
import java.net.Socket;
import java.sql.Connection;
/**
* 存在因为指令重排而导致的,别的对象还没被初始化,导致的,别的对象被使用的时候报空指针。
* @author wanghanwei.wb
*
*/
public final class SingletonDoubleCheckDanger {
private byte[] data = new byte[1024];
private static SingletonDoubleCheckDanger instance = null;
Connection conn;
Socket socket;
private SingletonDoubleCheckDanger() {
this.conn = null;// 初始化conn,这里用null代替步骤,大家意会就好
this.socket = null;// 初始化socket,这里用null代替步骤,大家意会就好
}
public static SingletonDoubleCheckDanger getInstance() {
if (instance == null) {
// 要保证所有的线程用的是同一个 单例对象,所以需要锁了class对象。
synchronized (SingletonDoubleCheckDanger.class) {
if (instance == null) {
instance = new SingletonDoubleCheckDanger();
}
}
}
return instance;
}
}
用关键字volatile
来防止指令重排。
正确代码
import java.net.Socket;
import java.sql.Connection;
public class SingletonDoubleCheckSafe {
private byte[] data = new byte[1024];
//关键词来一个,防止指令重排
private volatile static SingletonDoubleCheckSafe instance = null;
Connection conn;
Socket socket;
private SingletonDoubleCheckSafe() {
this.conn = null;// 初始化conn
this.socket = null;// 初始化socket
}
public static SingletonDoubleCheckSafe getInstance() {
if (instance == null) {
// 要保证所有的线程用的是同一个 单例对象,所以需要锁了class对象。
synchronized (SingletonDoubleCheckDanger.class) {
if (instance == null) {
instance = new SingletonDoubleCheckSafe();
}
}
}
return instance;
}
}
(六)Holder 方式
通过定义一个静态的内部类,根据类在初始化的时候会调用< clinit >()方法,来执行static成员变量赋值,以及执行static域的代码。< clinit >()方法是个同步方法,可以保证有序性,原子性,可见性,这个方法使用的最广。
public final class SingletonHolder {
private byte[] data = new byte[1024];
private SingletonHolder() {
}
private static class Holder {
private static SingletonHolder instance = new SingletonHolder();
}
public static SingletonHolder getInstance() {
return Holder.instance;
}
}
(七)枚举方式
《Effective Java》很推崇这种写法,但是本质上还是个饿汉思想实现的
public enum SingletonEnum {
INSTANCE;
private byte[] data = new byte[1024];
SingletonEnum() {
System.out.println(" instance will be initialized immediately.");
}
public static void method() {
// 调用的例子
System.out.println("22222222222222");
}
public void getOtherMethod() {
// 调用的例子
System.out.println("111111111111111");
}
public static SingletonEnum getInstance() {
return INSTANCE;
}
public static void main(String[] args0) {
SingletonEnum.method();
SingletonEnum.INSTANCE.getOtherMethod();
}
}