JAVA单例模式II双重检测锁 内部静态类 枚举 学习笔记
JAVA单例模式,除了懒汉式饿汉式这两种单例模式的实现方法以外还有双重检测锁、内部静态类、枚举、这几种虽然是不常用的实现方法但是也有其存在的好处。前面我已经讲解分析了饿汉式、懒汉式两种那么下面我将为大家介绍一下剩下的三种单例模式的实现方法。
双重检测锁:
双重检测锁是假如,我需要的这个单例类在需要被调用的时候才进行实例化操作,并且该类可能会被多个线程进行调用,这时候可以再懒汉式类添加一个,同步锁synchronized
但是我们也知道如果一个声明一个语句被执行是需要CPU进行运算处理的 特别是synchronized 这把名字叫做同步的锁在java中可谓是“沉重”的,如果把这把锁加在方法体外的话,如果我们的懒汉式方法已经被实例化了,根本不需要进行判断是否为已经被实例化过的单例方法,这种沉重的锁对于我们日常项目的运行速度可谓是一种负担,所以如何来来避免这种问题就是我们要讲的双重检测锁。
那么我们讲 双重检测的话首先要了解 synchronized 锁的作用:
在日常的开发中都不可避免的需要使用到 JAVA多线程,多线程能充分利用CPU资源分配处理器任务资源 。提高程序运行效率的一种技术,但是也存在一些弊端,例如处理线程需要浪费资源、线程的并发导致数据重复等。(详细的等到多线程的时候细细分析!)synchronized 方法是处理假如两个线程同时走一个单例方法时(带圈圈的数字是程序执行顺序!)
public class Singleton{
//未加锁方法
//假设有两个线程A 和B调用Singleton的未加锁getSingleton方法
//创建一个私有的静态Singleton类型等待赋值
private static Singleton singleton = null;
//私有化无参构造器
private static Singleton(){}
//① A线程通过Singleton类准备进入getSingleton 方法↓
//① B线程通过Singleton类准备进入getSingleton 方法↓
public static Singleton getSingleton ()
{
//② A线程进入getSingleton 方法此时singleton 变量为空↓
//② B线程进入getSingleton 方法此时singleton 变量为空↓
If(singleton == null){
//③ 因为A线程进入getSingleton 方法时singleton 变量为空 //所以进行实例化Singleton类↓
//③ 因为B线程进入getSingleton 方法时singleton 变量为空 //所以进行实例化Singleton类↓
singleton = new Singleton ();
//④然后AB两个线程返回 singleton A B线程结束↓
return singleton;
}else{
return singleton;
}
}
}
这样只会产生一个结果 该Singleton类不再是单例了但是如果在方法声明中加上同步锁的话就能解决这种问题。
public class Singleton{
//加锁方法
//继续假设有两个线程A 和B调用Singleton的加锁getSingleton方法
//创建一个私有的静态Singleton类型等待赋值
private static Singleton singleton = null;
//私有化无参构造器
private static Singleton(){}
//① A线程通过Singleton类准备进入getSingleton 方法↓
/**① B线程通过Singleton类准备进入getSingleton 方法单是因为A类已经进入线程锁被启动所以只能等待A线程执行结束*/
//⑦ 等待A线程执行结束的B线程开始执行↓
public static synchronized Singleton getSingleton ()
{
//② A线程进入getSingleton 方法此时singleton 变量为空↓
//⑧ B线程开始执行判断
//③ 因为A线程进入getSingleton 方法时singleton 变量为空
//⑨ 因为singleton 变量被A线程已经实例化了所以不为空
If(singleton == null){
//④ 所以进行实例化Singleton类↓
singleton = new Singleton ();
//⑤ 然后线程返回 singleton A线程结束关闭锁
//⑥ 开始B线程↓
//(注意!此时Singleton类型已被实例化)
return singleton;
}else{
//⑩ B线程执行else 并且返回singleton;
return singleton;
}
}
}
这样就可以解决因为线程问题导致的重复实例化对象问题但是这样的写法是有些浪费资源的。因为线程锁是进行堵塞其他线程来实现锁机制的声明。这个堵塞线程是耗费计算机资源的。所以如果把这个锁加载类中当 A线程进入该方法锁处于锁死状态,此刻是非常浪费计算机的时间的。因为这个单例方法已经实例化了就不会有线程安全问题所以可以直接进行返回实例化后的Singleton 类就不会浪费资源和线程的运算时间。 仔细想下如果这个类被很多线程调用全部堵塞在方法体外的话无疑是浪费CPU运算周期的事情。毕竟浪费就是可耻嘛,不过如果该类的调用率很高的话就采用饿汉式就好了。
所以这时候双重检测锁的优势就体现出来了:
public class Singleton{
//加锁方法
//继续假设有两个线程A 和B调用Singleton的双重检测锁的getSingleton方法
//创建一个私有的静态Singleton类型等待赋值
private static Singleton singleton = null;
//私有化无参构造器
private static Singleton(){}
//① A线程通过Singleton类准备进入getSingleton 方法↓
//① B线程通过Singleton类准备进入getSingleton 方法↓
//⑩假设一个C线程等AB两个线程执行结束后开始调用getSingleton 方法
public static Singleton getSingleton () {
//② A线程进入getSingleton 方法此时singleton 变量为空↓
//② B线程进入getSingleton 方法此时singleton 变量为空↓
//⑪ C线程开始判断因为A线程已经实例化singleton所以不为空↓
If(singleton == null){
/**③ 因为A线程进入getSingleton 方法时singleton 变量为空 所以进行下一层检测(注意!此时线程锁死)↓*/
/**③ 因为B线程进入getSingleton 方法时singleton 变量为空 但是线程锁死进入等待状态*/
//⑦ 线程锁开启 B线程开始进入线程锁内↓
synchronized (Singleton.class){
//④ A线程进入准备检测阶段因为singleton 变量为空所以进入↓
//(注意!A线程进入时该线程锁死)
//⑧ B线程进入锁中锁锁死因为singleton 不为空执行else代码块
If(singleton == null){
//⑤ A线程开始实例化单例方法↓
singleton = new Singleton ();
//⑥A线程获取实例化后的singleton
//(注意!此时A线程执行完了开锁现在是开启状态)
return singleton;
}{
//⑨ B线程获取实例化后的singleton 并且解锁
return singleton;
}
}
}
//⑫ C线程返回singleton
return singleton;
}
}
所以双重检测锁是最难理解的单例模式实现方法之一,在执行第二步骤的时候用C线程举例子来说就是如果这个类已经被前面的线程调用后者实例化过了就不会再继续走线程锁而是直接返回singleton如果没有被实例化将会使第一个线程去实例化(此时解锁)该类然后下个线程再进行判断是否已经实例化过然后返回singleton
但是双重检测锁不常用的原因有很多,例如双重检测锁处于很尴尬的局面他不够懒汉式的快速便捷开销也比普通懒汉式要大总的来说是双重检测锁是懒汉式的派生写法怎么取舍还是在编程中细细考量的好不过个人推荐吧使用较多的类 用饿汉式来写。
内部静态类:
内部静态类也可以叫静态内部类反正命名什么的怎么叫都是无所谓的只是一种利用java内部类和静态的机制来进行单例模式的实现使用也不常见 但是线程安全和懒汉式很是相近是用内存换时间的方式 不过如果让我来取舍的话我会选择 饿汉式 但是如果不经常调用该单例方法设计成饿汉式的优势就不如懒汉式了 【咳咳】跑题了
那么下面我为大家讲解下内部静态类的编写方法和思路。先来讲一下什么是静态内部类,静态内部类是在一个JAVA类中的类
public class demo{
public static void main(String[] args) {
// TODO Auto-generated method stub
}
private static class ishere{//静态的内部类
}
}
在写单例模式的静态内部类的实现方式前先了解下静态内部类的特性:
静态static 声明的类该声明方法会把此声明的变量方法加载到栈内存中,这样JAVA在进行实例化对象的时候。会把声明的静态变量方法的地址放在单独提取出来的内存中、以便快速调用。而且静态代码块在程序开始时执行时只执行一次这样对于我们单例模式来说就有一个好处。
静态内部类怎么实现单例模式
public class Singleton {
private Singleton(){}//私有无参构造器
private static class SingletonClass{//静态内部类
//虽然Singleton 类的构造器是私有化的但是该类在Singleton 内所以可以实例化
private static Singleton Singleton = new Singleton();
}
//返回一个已经实例化后的Singleton
public static Singleton getSingleton () {
return SingletonClass.Singleton;
}
}
静态内部类大致上和饿汉式没有什么太大的区别只不过是利用内部类的特性进行调用操作
枚举:
枚举是什么?JAVA编程中最容易被遗忘的类型:enum枚举类型
1、枚举类型的好处:
2、枚举变量当做返回值比较容易
3、枚举用于判断是一种高效率的方式
4、枚举是单例的
5、枚举返回值一定是存在的【不存在的话你写的时候就报错了兄弟!
那么为什么枚举不常见呢?
因为在日常学习中我们在编写程序时都忽略呢?因为枚举麻烦 在一般编程中也不容易被考虑到虽然效果比判断普通变量要好要快但是现在的计算机已经不用抠字眼的优化了【高并发的大型系统除外】所以在日常书写代码的时候使用的几率不高。下面为大家介绍下枚举单例模式。
普通的枚举类型书写方式:
public enum Singleton{
a,//一个枚举a “,”号分隔
b,//一个枚举b “,”号分隔
c;//一个枚举c “;”号中止枚举变量添加
}
枚举类型实现单例模式:
public enum Singleton{
a,//一个枚举a “,”号分隔
b,//一个枚举b “,”号分隔
c;//一个枚举c “;”号中止枚举变量添加
public static void Singleton() {//因为已经终止了枚举变量所以可以正常书写方法
// TODO Auto-generated method stub
}
}
也可以这样写:
public enum Singleton{
;//直接终结枚举添加然后正常书写方法
public static void Singleton() {
// TODO Auto-generated method stub
}
}
在Java中枚举虽然是5.0以后才更新的新成员 但是遭到了亲儿子一般的对待首先枚举自带提供序列化机制 绝对不会多次被实例化 利用枚举书写的单例模式还简洁。