单例模式的定义和特点:
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛,例如公司CEO,teamleader 等 。J2EE 中 的 ServletContext 、ServletContextConfig 等 、 Spring 框 架 应 用 中 的ApplicationContext、数据库的连接池等也都是单例形式。使用单例模式创建对象可以节省内存资源避免重复浪费内存创建对象实例,保证数据内容的一致性。
饿汉式单例模式(占着茅坑不拉屎不管用不用都创建):
类图:
代码:
public class HungrySingleton {
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
//构造方法私有化
private HungrySingleton () {
}
//提供一个全局访问点
public static HungrySingleton getInstance(){
return HUNGRY_SINGLETON;
}
}
除此之外还有1种利用java的类加载机制的顺序写在静态代码块的方式:
public class HungrySingleton2 {
private static final HungrySingleton2 HUNGRY_SINGLETON_2 ;
static {
HUNGRY_SINGLETON_2 = new HungrySingleton2();
}
//构造方法私有化
private HungrySingleton2() {
}
//提供一个全局访问点
public static HungrySingleton2 getInstance(){
return HUNGRY_SINGLETON_2;
}
}
总结:饿汉式单例的优点是没有加任何的锁,线程安全,执行效率比较高,不过由于在类加载的时候就初始化了,不论用或者不用,都创建了实例,会比较浪费内存。
懒汉式单例(在被调用的时候创建):
懒汉式单例解决了饿汉式单例占着茅坑不拉屎的问题,在被外部类调用时才会加载。
类图:
代码:
public class LazySingleton1 {
//注意这里不能用final修饰 否则无法动态根据外部调用获得实例
private static LazySingleton1 LAZY_SINGLETON_1 = null ;
//私有构造方法
private LazySingleton1() {
}
//全局访问点
public static LazySingleton1 getInstance() {
if(LAZY_SINGLETON_1 == null) {
LAZY_SINGLETON_1 = new LazySingleton1();
}
return LAZY_SINGLETON_1;
}
}
单线程下测试返回了我们期望的结果:
接下来我们用2个线程来跑一下这个单例:
先写一个线程的启动类在run方法中创建实例如下:
public class ExcutorThread implements Runnable {
@Override
public void run() {
LazySingleton1 lazySingleton1 = LazySingleton1.getInstance();
System.out.println(Thread.currentThread().getName() + " ==实例地址:===" + lazySingleton1);
}
}
测试方法:可以看到2种情况同一实例和不同实例出现了线程不安全问题
造成这个结果的原因是线程抢占cpu资源是随机的,如图:
当2个线程按照顺序先后执行时则会返回同一实例,如果不同时顺序执行则可能不同,也可能返回被覆盖的同一实例。
为了防止这种情况我们需要给我们的getInstance方法加上锁synchronized,当其他线程进入该方法时让其阻塞。如下通过断点调试发现只有获得了锁的线程被监视器监视了。得到的结果也是同一对象。
加锁虽然让线程安全的问题解决了。但是,用synchronized 加锁时,在线程数量比较多的情况下,如果 CPU 分配压力上升,则会导致大批线程阻塞,从而导致程序性能大幅下降。那么,有没有一种更好的方式,既能兼顾线程安全又能提升程序性能呢?
答案是肯定的,双重检查锁的单例模式因运而生。
双重检查锁的懒汉式单例模式:
public class LazySingleton1 {
//注意这里不能用final修饰 否则无法动态根据外部调用获得实例
//加入volatile内存屏障防止在赋值的时候重排序指令
private volatile static LazySingleton1 LAZY_SINGLETON_1 = null;
//私有构造方法
private LazySingleton1() {
}
//全局访问点
public static LazySingleton1 getInstance() {
if (LAZY_SINGLETON_1 == null) {
synchronized (LazySingleton1.class) {
if (LAZY_SINGLETON_1 == null) {
LAZY_SINGLETON_1 = new LazySingleton1();
//1.分配内存给这个对象
//2.初始化对象
//3.设置 LAZY_SINGLETON_1 指向刚分配的内存地址
}
}
}
return LAZY_SINGLETON_1;
}
}
当第一个线程调用 getInstance()方法时,第二个线程也可以调用。当第一个线程执行到synchronized 时会上锁,第二个线程就会变成 MONITOR 状态,出现阻塞。此时,阻塞并不是基于整个类的阻塞,而是在 getInstance()方法内部的阻塞,只要逻辑不太复杂,对于调用者而言感知不到。但是,用到 synchronized 关键字总归要上锁,对程序性能还是存在一定影响的。难道就真的没有更好的方案吗?当然有。我们可以从类初始化的角度来考虑,静态内部类单例模式因运而生。
静态内部类单例模式:
//利用内部类的加载机制
public class LazyInnerClassSingleton {
//私有构造器
private LazyInnerClassSingleton() {}
//提供一个全局的访问方法
//使用 LazyInnerClassSingleton的时候,默认加载外层的.class文件内部类用$LazyHolder指向
//如果没使用,则内部类是不加载的
//每一个关键字都不是多余的,static 是为了使单例的空间共享,保证这个方法不会被重写、重载
public static final LazyInnerClassSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
容器式单例:
public class ContainerSingleton {
private static ContainerSingleton CONTAINER_SINGLETON;
private ContainerSingleton() {
}
private static Map<String, ContainerSingleton> ioc = new ConcurrentHashMap<String, ContainerSingleton>();
public static ContainerSingleton getInstance() {
synchronized (ioc) {
if (ioc.containsKey("instance")) {
return ioc.get("instance");
} else {
CONTAINER_SINGLETON = new ContainerSingleton();
try {
ioc.put("instance", CONTAINER_SINGLETON);
} catch (Exception e) {
e.printStackTrace();
}
return CONTAINER_SINGLETON;
}
}
}
}
枚举单例:
public enum EnumSingleton {
/**
* 枚举实例
*/
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getIns() {
return INSTANCE;
}
}