单例模式:一个类保证全局只有一个实例,并提供全局访问点
创建单例的方式很多这里主要介绍其中4种:饿汉式、懒汉式、静态内部类及枚举
饿汉式
顾名思义饿汉式创建单例就是无论有没有使用到这个单例都会在类加载的时候被创建出来,JVM保证线程安全,是最简单的创建方式,唯一缺点是没有做到按需创建。以下是通过静态变量实例化,通过getInstance()方法获取使用。
/**
* @author lyy
* @Title: 单例模式
* @description:饿汉式,类加载到内存后就实例化一个单例,JVM保证线程安全,简单实用,但是唯一缺点就是不管用到与否类加载的时候就会实例化
* @date 2021/12/13 10:47
*/
public class Singleton01 {
private static final Singleton01 INSTANCE = new Singleton01();
//构造器设置为私有,创建实例必须从gerInstance()中获取 静态的INSTANCE实例
private Singleton01(){};
public static Singleton01 getInstance(){return INSTANCE;};
public static void main(String[] args) {
Singleton01 instance01 = Singleton01.getInstance();
Singleton01 instance02 = Singleton01.getInstance();
System.out.println(instance01 == instance02);//true ,表示是同一个实例
}
}
懒汉式
懒汉式创建单例,可以做到按需创建的目的,但是不能保证线程安全,需要保证线程安全需要添加锁。
线程不安全情况:
/**
* @author lyy
* @Title:lazy loading 懒汉式
* @description:达到了按需实例化的要求,但是会带来线程不安全问题
* @date 2021/12/13 10:47
*/
public class Singleton02 {
//不能加final, 加final必须初始化;
private static Singleton02 INSTANCE;
private Singleton02 (){}
//先判断INSTANCE是否存在,已存在就不用创建
public static Singleton02 getInstance(){
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Singleton02();
}
return INSTANCE;
}
public static void main(String[] args) {
//懒汉式(存在多线程问题),
for (int i = 0; i < 100 ; i++) {
new Thread(() ->{
/**
* 打印发现100个hash地址中会有不一样的值,所以说明不是同一个实例
*/
System.out.println(Singleton02.getInstance().hashCode());
}).start();
}
}
}
输出结果发现哈希地址不唯一说明线程不安全
线程安全的情况:
/**
* @author lyy
* @Title:懒汉式加synchronized可以解决线程安全问题,但同时也会造成效率下降
* @description:
* @date 2021/12/13 10:47
*/
public class Singleton03 {
//不能加final, 加final必须初始化;
private static Singleton03 INSTANCE;
private Singleton03 (){}
//先判断INSTANCE是否存在,已存在就不用创建
public static synchronized Singleton03 getInstance(){
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Singleton03();
}
return INSTANCE;
}
public static void main(String[] args) {
//懒汉式(存在多线程问题),
for (int i = 0; i < 100 ; i++) {
new Thread(() ->{
/**
* 打印发现100个hash地址中会有不一样的值,所以说明不是同一个实例
*/
System.out.println(Singleton03.getInstance().hashCode());
}).start();
}
}
}
从上面代码可以发现每次都需要竞争锁,会导致效率很低,所以为了提高效率我们使用同步代码块的方式创建单例如下:
/**
* @author lyy
* @Title:懒汉式通过同步代码块的方式提高加锁后效率低下的问题,然而并不能解决线程安全问题
* @description:
* @date 2021/12/13 10:47
*/
public class Singleton04 {
//不能加final, 加final必须初始化;
private volatile static Singleton04 INSTANCE;
private Singleton04 (){}
//先判断INSTANCE是否存在,已存在就不用创建
public static Singleton04 getInstance(){
if (INSTANCE == null) {
//同步代码块
synchronized(Singleton04.class){
try {
Thread.sleep(1);
} catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Singleton04();
}
}
return INSTANCE;
}
public static void main(String[] args) {
//懒汉式(存在多线程问题),
for (int i = 0; i < 100 ; i++) {
new Thread(() ->{
/**
* 打印发现100个hash地址中会有不一样的值,所以说明不是同一个实例
*/
System.out.println(Singleton04.getInstance().hashCode());
}).start();
}
}
}
致此效率问题是提高了,但是却造成了致命的线程不安全问题,因为当两个线程同时判断到instance为null时会一起竞争锁,等上一个线程创建完单例之后,下一个线程获得锁又会创建一个单例。执行结果如下
因此便出现了双层检查解决效率低下及线程安全的问题
/**
* @author lyy
* @Title:懒汉式双层检查解决效率低下及线程安全问题
* @description:
* @date 2021/12/13 10:47
*/
public class Singleton05 {
//不能加final, 加final必须初始化;
private volatile static Singleton05 INSTANCE;
private Singleton05 (){}
//先判断INSTANCE是否存在,已存在就不用创建
public static Singleton05 getInstance(){
//第一层检查
if (INSTANCE == null) {
//同步代码块
synchronized(Singleton05.class){
// 第二层检查
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Singleton05();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
//懒汉式(存在多线程问题),
for (int i = 0; i < 100 ; i++) {
new Thread(() ->{
/**
* 打印发现100个hash地址中会有不一样的值,所以说明不是同一个实例
*/
System.out.println(Singleton05.getInstance().hashCode());
}).start();
}
}
}
静态内部类
因为外部类加载的时候不会加载内部类,所以使用静态内部类的方式创建单例可以保证线程安全,同时也可以实现懒加载
/**
* @author lyy
* @Title:静态内部类的方式创建单例,JVM保证单例
* @description:
* @date 2021/12/13 10:47
*/
public class Singleton06 {
//私有构造器
private Singleton06 (){}
//静态内部类的方式,只实例化一次,后面每次调用外部类都不会加载内部类
private static class Singleton6Holder{
private static final Singleton06 INSTANCE = new Singleton06();
}
public static Singleton06 getInstance(){
return Singleton6Holder.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
new Thread(() ->{
/**
* 打印发现100个hash地址中会有不一样的值,所以说明不是同一个实例
*/
System.out.println(Singleton06.getInstance().hashCode());
}).start();
}
}
}
枚举Enum
使用枚举创建单例不仅可以保证线程安全还可以防止序列化,因为枚举不会存在构造函数
/**
* @author lyy
* @Title:使用枚举enum,不仅可以解决线程同步,还可以防止反序列化
* @description:
* @date 2021/12/13 10:47
*/
public enum Singleton07 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
new Thread(() ->{
/**
* 打印发现100个hash地址中会有不一样的值,所以说明不是同一个实例
*/
System.out.println(Singleton07.INSTANCE.hashCode());
}).start();
}
}
}