单例设计模式
使用单例模式的场景
- 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
1. 饿汉式单例模式
- 在类加载时就创建类对象,将类的构造器私有化,不让外界对该类进行实例化,实例化的操作交给类本身
1. 不使用静态代码块方式
* 在明确要使用到该对象时推荐使用
/**
* 饿汉式单例模式:在类加载时就创建类对象
* 细节:(不使用静态代码块的方式)
* 将类的构造器私有化,不让外界对该类进行实例化,实例化的操作交给类本身
*
* @author : 可乐
* @version : 1.0
* @since : 2021/7/17 10:25
*/
public class SingLeton01 {
// 将构造器私有化
private SingLeton01(){}
// 创建一个静态类对象变量用于接收创建好的类对象
private static final SingLeton01 INSTANCE = new SingLeton01();
// 创建一个get方法用于获得
public static SingLeton01 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
SingLeton01 singLeton01 = new SingLeton01();
SingLeton01 singLeton2 = new SingLeton01();
System.out.println(singLeton01.getInstance());
System.out.println(singLeton2.getInstance());
}
}
2. 使用静态代码块方式
* 在明确要使用到该对象时推荐使用
/**
* 饿汉式单例模式:在类加载时就创建类对象
* 细节:(使用静态代码块的方式)
* 将类的构造器私有化,不让外界对该类进行实例化,实例化的操作交给类本身
*
* @author : 可乐
* @version : 1.0
* @since : 2021/7/17 10:43
*/
public class SingLeton02 {
// 将构造器私有化
private SingLeton02() {}
// 在静态代码块中对类对象进行创建
static {
INSTANCE = new SingLeton02();
}
// 创建一个静态类对象变量用于接收创建好的类对象
private static final SingLeton02 INSTANCE;
// 创建一个get方法用于获得
public static SingLeton02 getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
SingLeton02 singLeton01 = new SingLeton02();
SingLeton02 singLeton2 = new SingLeton02();
System.out.println(singLeton01.getInstance());
System.out.println(singLeton2.getInstance());
}
}
2. 懒汉式单例模式
- 在使用的时候再创建该类对象,将类的构造器私有化,不让外界对该类进行实例化,实例化的操作交给类本身
1. 线程不安全的懒汉式单例(开发中不使用)
/**
* 懒汉式单例模式(线程不安全的懒汉式)
* 在实际调用的时候才创建对象
* 将类的构造器私有化,不让外界对该类进行实例化,实例化的操作交给类本身
* @author : 可乐
* @version : 1.0
* @since : 2021/7/17 17:12
*/
public class SingLeton03 {
// 将构造器私有化
private SingLeton03() {}
// 创建一个静态类对象变量用于接收创建好的类对象
private static SingLeton03 INSTANCE = null;
// 创建一个get方法用于获得
public static SingLeton03 getInstance() {
/*
判断当前对象是否已经创建,如果没有创建,则创建
1.在这个位置可能会发生线程安全问题
2.当线程A和线程B都走到当前位置那个线程拿到的 INSTANCE 都是null,
所以两个线程都进行了对象创建的操作,主要就创建了两个对象,就不是单例模式了
*/
if (INSTANCE == null){
INSTANCE = new SingLeton03();
}
// 如果已经创建过则直接返回
return INSTANCE;
}
public static void main(String[] args) {
SingLeton03 singLeton01 = new SingLeton03();
SingLeton03 singLeton02 = new SingLeton03();
System.out.println(singLeton01.getInstance() == singLeton02.getInstance());
}
}
2. 使用同步方法保证懒汉式安全(开发中不推荐使用)
/**
* @author : 可乐
* @version : 1.0
* @since : 2021/7/17 17:46
*/
public class SingLeton04 {
// 将构造器私有化
private SingLeton04() {}
// 创建一个静态类对象变量用于接收创建好的类对象
private static SingLeton04 INSTANCE = null;
/*
创建一个get方法用于获得,
直接将该静态方法用 synchronized 同步锁将其锁住,
锁住静态方法就相当于锁住了当前类对象,因为静态方法是属于类的,此时只允许一个线程操作该对象
*/
public static synchronized SingLeton04 getInstance() {
if (INSTANCE == null){
INSTANCE = new SingLeton04();
}
// 如果已经创建过则直接返回
return INSTANCE;
}
public static void main(String[] args) {
SingLeton04 singLeton01 = new SingLeton04();
SingLeton04 singLeton02 = new SingLeton04();
System.out.println(singLeton01.getInstance() == singLeton02.getInstance());
}
}
3. 使用双层校验的方式保证懒汉式安全(推荐使用)
/**
* 使用双层校验的方式保证懒汉式安全(开发中推荐使用)
* @author : 可乐
* @version : 1.0
* @since : 2021/7/17 18:02
*/
public class SingLeton05 {
// 将构造器私有化
private SingLeton05() {
}
/*
创建一个静态类对象变量用于接收创建好的类对象
使用volatile 轻量锁将变量锁住
1. 保证了不同线程对这个变量进行操作时的可见性,
即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2. 禁止进行指令重排序
*/
private static volatile SingLeton05 INSTANCE;
// 创建一个get方法用于获得
public static SingLeton05 getInstance() {
/*
判断当前的 INSTANCE 是否为null,
当后面的线程进入此代码的时候,如果已经创建了对象,不会再进入到里面的代码保证了效率
*/
if (INSTANCE == null) {
/*
将当前类锁住,避免其他线程操作该类
1. 当线程A到这来先获得锁,线程B在外面等待,那么在 INSTANCE 为null的情况下就会创建对象
此时因为 INSTANCE 被 volatile 修饰,使用线程B会立即知道被修改过的值
2. 当线程A在获得锁进来时,此时 INSTANCE 已经不是 null 所以不会再进行对象的创建
*/
synchronized (SingLeton05.class) {
// 此时如果
if (INSTANCE == null) {
INSTANCE = new SingLeton05();
}
}
INSTANCE = new SingLeton05();
}
// 如果已经创建过则直接返回
return INSTANCE;
}
}
4. 通过静态内部类方式创建单例对象(推荐使用)
/**
* 通过静态内部类方式创建单例对象
* @author : 可乐
* @version : 1.0
* @since : 2021/7/17 18:43
*/
public class SingLeton06 {
// 将构造器私有化
private SingLeton06() {}
/*
创建静态内部类方式创建单例对象,
在类SingLeton06加载的时候不会创建静态内部类对象,
只有在被调用时才加载,并且只加载一次,在类进行加载的时候是线程安全的,别的线程无法操作
*/
public static class getSingLeton06Instance {
private static SingLeton06 INSTANCE = new SingLeton06();
}
// 创建一个get方法用于获得
public static SingLeton06 getInstance() {
return getSingLeton06Instance.INSTANCE;
}
}
3. 使用枚举的方式实现单例(推荐使用)
package com.kl.desginMode.singleton;
/**
* 使用枚举的方式实现单例
* 1. 支持序列化
* 2. 线程安全
* 3. 保证单例
*
* @author : 可乐
* @version : 1.0
* @since : 2021/7/17 19:05
*/
enum SingLeton07 {
INSTANCE;
}
4. JDK源码中使用到的单例模式
/**
* Runtime类中的单例模式
*/
public class Runtime {
// 创建一个静态变量,接收一开始创建的Runtime对象
private static Runtime currentRuntime = new Runtime();
// 构造方法
private Runtime() {}
// getRuntime方法
public static Runtime getRuntime() {
return currentRuntime;
}
// 剩余方法不作说明......
}