Java单例模式的不同写法(懒汉式、饿汉式、双检锁、静态内部类、枚举)
简介
在Java中单例(Singleton)模式是经常用到的一种设计模式;单例模式的主要作用是保证在Java程序中,类只有一个实例存在;它能保证以下几种好处
1.可以避免实例对象的重复创建,实例对象没有重复创建,间接的就对资源的开销减少,资源的开销减少就减少时间的开销,内存空间的节省,有利于Java垃圾的回收
2.可以保证一个类公有唯一的实例,可以避免多个实例导致的错误
单例模式的特点
1.保证自己只有一个实例
2.必须是自己创建自己的实例
3.为外界提供唯一获取这个实例的唯一方法(私有化构造方法)
单例模式的写法
1.饿汉式
package com.jbit.mysingle;
/**
* 饿汉式:最开始就创建好对象
*/
public class Single1 {
//自己给自己new一个对象并且赋值
private static final Single1 INSTANCE=new Single1();
//将构造方法私有化,外界无法通过new的形式来创建这个类的对象
private Single1(){
}
//给外界提供唯一的通过类创建对象的方法
public static Single1 getInstance(){
return INSTANCE;
}
public void print(){
System.out.println("饿汉模式");
}
public static void main(String[] args) {
Single1 s1=Single1.getInstance();
Single1 s2=Single1.getInstance();
System.out.println(s1==s2);
}
}
好处:在加载类的时候就把实例创建好并且提供对外创建这个实例的方法,就算在多线程环境下也不会存在创建多个实例的情况,可以避免多线程同步的问题
缺点:就是不管这个类是否使用到它都会创建对象(Class.forName("")),浪费内存
使用场景:一般都是限定了这个类一开始,也就是初始化后就要使用到
2.懒汉
package com.jbit.mysingle;
/**
* 懒汉:需要时才创建对象
*/
public class Single2 {
//创建出一个类的对象,但是没有赋值
private static Single2 INSTANCE;
//私有化构造方法,自己提供特定获得对象的方法
private Single2() {
}
//自己提供特定获得对象的方法
//如果为null贼创建并且赋给自己类里面的对象
//如果不为null就将值直接返回(这时值已经是不为空的了)
public static Single2 getInstance() {
if (null == INSTANCE) {
INSTANCE = new Single2();
}
return INSTANCE;
}
}
好处:需要的时候才去创建,假如单例已经创建,它就根据if条件去判断就不会创建了,就直接返回创建好的对象
缺点:这里的环境没有考虑到多线程的情况,假设现在多种线程同时去调用它的getInstance()方法,这时是INSTANCE是没有值的,就会创建出多个实例
使用场景:使用的次数少,创建这个单例消耗的资源多
懒汉模式的解决办法
1.给方法加synchronized关键字
package com.jbit.mysingle;
/**
* 懒汉:需要时才创建对象
*/
public class Single3 {
//创建出一个类的对象,但是没有赋值
private static Single3 INSTANCE;
//私有化构造方法,自己提供特定获得对象的方法
private Single3() {
}
//自己提供特定获得对象的方法
//如果为null贼创建并且赋给自己类里面的对象
//如果不为null就将值直接返回(这时值已经是不为空的了)
public synchronized static Single3 getInstance() {
if (null == INSTANCE) {
INSTANCE = new Single3();
}
return INSTANCE;
}
}
2.给代码块加synchronized关键字
package com.jbit.mysingle;
/**
* 懒汉:需要时才创建对象
*/
public class Single4 {
//创建出一个类的对象,但是没有赋值
private static Single4 INSTANCE;
//私有化构造方法,自己提供特定获得对象的方法
private Single4() {
}
//自己提供特定获得对象的方法
//如果为null贼创建并且赋给自己类里面的对象
//如果不为null就将值直接返回(这时值已经是不为空的了)
public static Single4 getInstance() {
//想通过代码块的形式来加快效率的提升,但不太可行
//当第一个线程来的时候判断为null还没继续第二个线程来了,第二个线程申请上锁,锁好了执行代码new对象,然后执行方法释放锁;然后第一个线程继续运行,申请锁,new对象...
synchronized(Single4.class){
if (null == INSTANCE) {
INSTANCE = new Single4();
}
}
return INSTANCE;
}
}
这两种虽然解决了在多线程环境下的多实例情况,但资源的浪费大大提高(本身synchronized修饰的东西就比一般的慢,而且当你外面有一大堆业务逻辑的时候,你的多个线程只能在外面等待,非常浪费资源)
所以就引出了现在推荐使用的双重校验锁(DCL(Double Check Lock))
双重校验锁(DCL(Double Check Lock))
package com.jbit.mysingle;
public class Single5 {
private static Single5 INSTANCE;
private Single5(){
}
public static Single5 getInstance(){
//业务逻辑代码(如果不为空就直接返回对象执行)
if(null == INSTANCE){ //Twice Checked
//双重检查
synchronized (Single5.class){
if(null == INSTANCE){ //Double Checked
INSTANCE=new Single5();
}
}
}
return INSTANCE;
}
}
这种方式很好的减少了多线程及性能的问题,大部分的代码都不会执行到synchronized这里,因为上面有一层if(null == INSTANCE)的判断;而里面这一层if(null == INSTANCE)为什么需要呢?假如没有的话,多个线程同时过了第一层的if(null == INSTANCE)的时候,多个线程进入到了synchronized代码块里面,就会分别创建这个对象
虽然双重校验锁实现了单例对象,线程并发问题,执行效率问题,但也不一定会万无一失,那是为什么呢?这里是因为CPU的乱序扫行,那么什么是乱序执行呢?
乱序执行
因为CPU的速度是内存的100倍的原因;当指令A发比例内存时,假设等待10秒,而指令B的速度只要3秒,那么它现在很有可能先执行B指令,B指令执行完了再执行A指令(谁先快先执行谁,不管顺序,底层是汇编实现的),怎么禁止?
答案是加vloatile
volatile
volatile关键字规则凡是被它修饰的变量,对这个变量进行指令操作的时候都要加屏障,屏障就是上面一条指令,下面一条指令的时候中间给你加一道墙,这个墙就是内存屏障,内存屏障这里就不多说了;volatile的特性就很好解决了这个问题
所以修改后代码为这样
package com.jbit.mysingle;
public class Single5 {
private static volatile Single5 INSTANCE;
private Single5(){
}
public static Single5 getInstance(){
//业务逻辑代码(如果不为空就直接返回对象执行)
if(null == INSTANCE){ //Twice Checked
//双重检查
synchronized (Single5.class){
if(null == INSTANCE){ //Double Checked
INSTANCE=new Single5();
}
}
}
return INSTANCE;
}
}
静态内部类
package com.jbit.mysingle;
/**
* 静态内部类
*/
public class Single6 {
//通过内部类的实现去创建本类的对象
private static class Single6Inside{
public static Single6 instance=new Single6();
}
private Single6(){
}
//给外界提供创建对象的方法
public static Single6 newInstance(){
return Single6Inside.instance;
}
}
静态内部类的实现其实和饿汉有点类型,都是通过类加载的时候就创建对象得到的;但不一样的是它是通过内部类去创建对象实例的,也就是说只要不使用这个内部类,就加载不到这个单例类,自然也无法创建
枚举
public enum Single7 {
INSTANCE;
}
获取方式及结果返回
现在很强烈使用枚举,至于为什么可以看:https://cloud.tencent.com/developer/article/1497592
上一篇: Linux安装配置JDK
下一篇: Java小白学习笔记d2