单例模式学习心得
简介
什么是单例
一个类只有一个实例,并且提供一个全局访问点为什么需要单例模式
对于一些简单类型对象的创建如:int room = 806 、String school = "edu.zsc.bdic" 这些类型的对象创建和销毁对性能影响不大
有些对象的创建过程是庞大而复杂的,并且这些对象完全是可以复用的,如果频繁的创建和销毁将影响性能,如:
数据库连接对象
配置对象
实现单例模式需要考虑那些点
1、是否是线程安全的:在多线程并发情况下是否仍能保证获取同一实例
2、是否支持懒加载:如果在类加载的时候就实例化,万一一直不使用则容易浪费内存,我们希望在调用的时候再实例化,这是更加合理的做法。
3、是否被反射破环(这种方式属于人为破坏,暂时不考虑)
单例实现方式
单例模式实现方法的通式:1.构造方法私有化 2.提供全局访问点
1.饿汉式
//饿汉式1
public class Singleton1 {
//将实例声明为静态常量,在类加载的时候就实例化
private static final Singleton1 instance = new Singleton1();
// 私有构造函数,防止实例化
private Singleton1() {
}
// 提供全局访问点
public static Singleton1 getInstance(){
return instance;
}
}
是否线程安全:√
是否支持懒加载:×
2.懒汉式
//懒汉式1
public class Singleton1 {
private static Singleton1 instance;
// 私有构造函数,防止实例化
private Singleton1() {
}
public static getInstance(){
//此处存在线程安全问题,可能多个线程同时进入if语句块
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
}
public class Test {
public static void main(String[] args) {
Thread t1 = new MyThread();
Thread t2 = new MyThread();
t1.start();
t2.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println(Singleton1.getInstance());
}
}
//两个线程的输出结果
edu.zsc.bdic.singleton2.Singleton1@4e704f06
edu.zsc.bdic.singleton2.Singleton1@65d2066b
是否线程安全:×
是否支持懒加载:√
3.懒汉式2(校验锁)
//懒汉式2
public class Singleton2 {
/*
volatile 关键字确保 instance 的可见性和有序性
它防止了指令重排序可能导致的问题,使得一个线程看到一个未完全初始化的对象
*/
private static volatile Singleton2 instance;
private Singleton2() {
// 私有构造函数,防止实例化
}
public static synchronized Singleton1 getInstance(){
//此处存在线程安全问题,可能多个线程同时进入if语句块 需要加锁,但是会影响性能
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
是否线程安全:√
是否支持懒加载:√
线程需要排队,性能一般
4.懒汉式3(双重校验锁)
//懒汉式3 DCL(双重锁)
public class Singleton2 {
/*
volatile 关键字确保 instance 的可见性和有序性
它防止了指令重排序可能导致的问题,使得一个线程看到一个未完全初始化的对象
*/
private static volatile Singleton2 instance;
private Singleton2() {
// 私有构造函数,防止实例化
}
public static Singleton2 getInstance(){
if (instance == null) { //一重校验:检查是否已经存在实例,避免进入同步代码块
synchronized (Singleton2.class){
if (instance == null) { //二重校验:避免第一个已经创建了实例的线程释放了锁后,其他线程重复创建实例
instance = new Singleton2();
}
}
}
return instance;
}
}
instance = new Singleton2();
最主要是因为这一行代码不具备原子性
它可以被差分为三部分:
1.分配内存
2.初始化对象
3.设置引用指向堆中的对象地址
jvm重排序的时候会打乱它们的顺序,使一个线程看到一个未完全初始化的对象
是否线程安全:√
是否支持懒加载:√
实现比较复杂
5.静态内部类
//静态内部类
public class Singleton1 {
private Singleton1() {
// 私有构造函数,防止实例化
}
// 静态内部类运行机理:JVM加载类的时候是不会加载静态内部类
// 只有当内部类的属性/方法被访问的时候才会加载
private static class SingletonHolder{
private static final Singleton1 INSTANCE = new Singleton1();
}
public static Singleton1 getInstance(){
return SingletonHolder.INSTANCE;
}
}
这种写法巧妙的利用了jvm加载类的机制:静态内部类不会随着外部类的加载而自动加载。JVM在加载包含静态内部类的外部类时,并不会立即加载静态内部类。只有当静态内部类被显式引用时,JVM才会加载它。
是否线程安全:√
是否支持懒加载:√
心得
单例模式作为设计模式里面一种简单的模式,它包含了线程安全、内存模型、类加载机制等等一些核心的知识点。
认识到了单例模式的几种方式:饿汉式,懒汉式,双重校验锁,静态内部类,也了解到了它们各自的特点。
虽然单例模式是一种简单的设计模式,但是它考验了程序员的基本功是否扎实,在学习的过程中我发现了自己对这方面的了解确实有所欠缺。
参考
https://mp.weixin.qq.com/s/nKsKOIdV-vt-tAEs5mqlpw
B站:BV1pt4y1X7kt
单例模式学习心得
http://localhost:8090//archives/dan-li-mo-shi-xue-xi-xin-de