常见的场景
(1)比如说,你自定义了一个框架,自定义了一份xml格式的一个配置文件,你要读取这个配置文件,这个配置文件中的数据,读取到类中,这个类的实例,只要保存一份就可以。那么此时可以使用单例模式,将这个类做成他的实例只能有一个,在这个实例中保存了配置文件中的数据 (2)类似于我们之前给大家讲解的那个工厂模式,有些工厂是需要实例化对象的,要基于实例化对象,来实现继承、接口、实现等功能,工厂实例可以做成单例的,就可以了 (3)你自己去判断,如果是一个类的实例只需要保持一份,那就做成单例
1.饿汉模式
package com.example.demo.pattern.singleton;
/**
* 饿汉模式
* @author chenchao
*
*/
public class HungrySingletonPatternDemo {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
singleton.execute();
new Singleton();
}
public static class Singleton {
/**
* 第一步:直接就是将这个类的实例在创建出来,赋予static final修饰的变量
*
* static:就是一个类的静态变量
* final:这个变量的引用第一次初始化赋予之后,就再也不能修改引用了
*
*/
private static final Singleton instance = new Singleton();
/**
* 第二步:将构造函数搞成private私有的
*
* 此时除了这个类自己本身,其他任何人都不能创建它的这个实例对象
*
*/
private Singleton() {
}
/**
* 第三步:给一个static静态方法,返回自己唯一的内部创建的一个实例
* @return
*/
public static Singleton getInstance() {
return instance;
}
public void execute() {
System.out.println("单例类的方法");
}
}
}
2.常用内部内
package com.example.demo.pattern.singleton;
/**
* 这个才是我们实际开发过程中,最最常用的单例模式,内部类的方式来实现
*
* @author chenchao
*
*/
public class InnerClassFullSingletonPatternDemo {
/**
* 可以做饱汉模式
*
* 内部类,只要没有被使用,就不会初始化,Singleton的实例就不会创建
*
* 在第一次有人调用getInstance方法的时候,内部类会初始化,创建一个Singleton的实例
*
* 然后java能确保的一点是,类静态初始化的过程一定只会执行一次
*
* @author chenchao
*
*/
public static class Singleton {
private Singleton() {
}
public static class InnerHolder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return InnerHolder.instance;
}
}
}
3.饱汉模式 线程不安全
package com.example.demo.pattern.singleton;
/**
* 线程不安全的饱汉模式
* @author chenchao
*
*/
public class UnsafeFullSingletonPatternDemo {
/**
* 线程不安全
*
* @author chenchao
*
*/
public static class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
/*
* 假设有两个线程过来
*
* 线程的基础:线程是并发着执行的,cpu,先执行一会儿线程1,然后停止执行线程1;切换过去执行线程2
* 执行线程2一会儿之后,再停止执行线程2;回来继续执行线程1
*
* 第一个线程,判断发现说instance == null,代码就进入到了下面去
* 第二个线程,执行到这儿,发现,此时instance == null,那么就没什么问题了,继续往下走
*
*/
if(instance == null) {
// 第一个线程跑到了这儿来,但是此时第一个线程,还没有执行下面的那行代码
// 此时,第二个线程代码也执行到了这儿,cpu切换回线程1
// 执行线程1的代码,线程1会创建一个实例出来
// 但是切换到线程2去执行的时候,线程2,的代码已经执行到这儿来了,此时又会再一次执行下面的代码
// 就是会再一次创建一个实例,之前线程1创建的那个实例,就会被垃圾回收,废弃掉了
instance = new Singleton();
}
return instance;
}
}
}
4. 线程安全 double check Lock DCL
虽然上了DCL双重检查但是还是有一定几率线程不安全,推荐用第二种内部内方式,基于JDK线程安全。
package com.example.demo.pattern.singleton;
/**
* 饿汉模式
* @author chenchao
*
*/
public class SafeFullSingletonPatternDemo {
public static class Singleton {
private static Singleton instance;
private Singleton() {
}
// 不是完美的
// 因为不同的JVM的编译器的问题,可能导致说,这个情况下,还是线程不安全的
// 具体的我不再这儿讲,因为涉及到复杂的JVM内部的原理
public static Singleton getInstance() {
// 如果线程1和线程2都执行到了这一步,然后此时线程1判断发现还是null
// 线程2此时判断发现instance == null,也会进去
if(instance == null) {
// 线程1就会进来,此时线程1停止,切换到线程2
// 线程2也会进来,此时切换到线程1
// 线程1,发现这里需要加锁, 在这里加锁,获取到了这个锁
// 线程2过来,线程2发现说,我也想要在这里加锁,发现说这个锁被人加了,线程2挂起等待别人释放锁
// 此时切换回线程2,线程2发现锁被释放,然后在这里加锁
synchronized(SafeFullSingletonPatternDemo.class) {
// 线程1就进来了,此时切换到线程2
// 切换回线程1,线程1此时在这里,再次判断,instance == null
// 线程2就进来了,double check,如果这里没有instance == null的判断,那么线程2就会再次创建
// 一个实例
// 但是这里是双重检查,线程2又判断了一下,instance == null?否,不是null
if(instance == null) {
// 线程1就会进来,创建一个实例
instance = new Singleton();
}
}
}
// 这边出来以后,线程1就释放锁了
// 线程2跳出来,直接获取一个instance返回了,这个instance就是之前线程1创建的实例
return instance;
}
}
}
5.说明
最简单的一种类了,就是如果一个类就只需要一次,那么就使用这个类了
但是使用单例模式有一个要求,不允许这个类的逻辑过于复杂,一般就是持有某份配置文件的配置,或者是别的一些数据