Java单例模式分析

一、单例模式的介绍与定义

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

二、单例模式实现方式

  • 饿汉模式
  • 懒汉模式

1、恶汉模式单例

(1)、实现步骤

第一步:

构造方法的私有化,不允许外部直接创建对象。

第二步:

创建类的唯一实例,使用private static修饰

第三步:

提供一个用于获取实例的方法,使用public static修饰

(2)、饿汉模式的特点

在类加载的时候创建一个唯一实例,还没调用对象的时候就被创建了。

(3)、具体代码实现

/******************需要设置单例模式的类*********************/
/**
 * 1、饿汉模式:在类加载的时候创建一个唯一实例,还没调用对象的时候就被创建了
 */
public class SingLeton {

    /**
     * 1、构造方法的私有化,不允许外部直接创建对象
     *
     */
    private SingLeton() {

    }

    /**
     * 2、创建类的唯一实例,使用private static修饰
     */
    private static SingLeton singLeton = new SingLeton();

    /**
     * 3、提供一个用于获取实例的方法,使用public static修饰
     */
    public static SingLeton singLeton() {

        return singLeton;
    }
}

/******************客户端的调用*********************/
public class SingletonTest {

    public static void main(String[] arg){

        /***********饿汉模式*************/
        SingLeton s1 = SingLeton.singLeton();
        SingLeton s2 = SingLeton.singLeton();

        if (s1 == s2) {
            System.out.println("s1和s2是同一个实例");
        }else {
            System.out.println("s1和s2不是同一个实例");
        }
    }
}

/******************返回结果*********************/
s1和s2是同一个实例

2、懒汉模式单例

(1)、实现步骤

第一步:

构造方法的私有化,不允许外部直接创建对象。

第二步:

创建类的唯一实例,使用private static修饰,但是是声明类的实例,没有去实例化

第三步:

提供一个用于获取实例的方法,使用public static修饰。

(2)、懒汉模式的特点

在类调用运行的时候去判断是否创建对象了,如果没创建一个,也就是第一次会创建,后面就不会再创建了。

(3)、具体代码实现

/******************实现懒汉模式的类***********************/
/**
 * 懒汉模式
 * 区别:饿汉模式的特点,加载类时比较慢,但运行时获取对象的速度比较快,是线程安全的
 *      懒汉模式的特点,加载类时比较快,但运行时获取对象的速度比较慢,是非线程安全的
 */
public class SingLeton2 {

    /**
     * 1、构造方法的私有化,不允许外部直接创建对象
     */
    private SingLeton2() {

    }

    /**
     * 2、创建类的唯一实例,使用private static修饰
     */
    private static SingLeton2 singLeton2; //声明类的实例,没有去实例化

    /**
     * 3、提供一个用于获取实例的方法,使用public static修饰
     */
    public static SingLeton2 getSingLeton2(){

        if (singLeton2 == null) {
            return new SingLeton2();
        }
        return singLeton2;
    }
}

/******************客户端调用***********************/
public class SingletonTest {

    public static void main(String[] arg){

        /***********懒汉模式*************/
        SingLeton2 s3 = SingLeton2.getSingLeton2();
        SingLeton2 s4 = SingLeton2.getSingLeton2();
        if (s3 == s4) {
            System.out.println("s3和s4是同一个实例");
        }else {
            System.out.println("s3和s4不是同一个实例");
        }
    }
}



/******************返回结果***********************/
s3和s4是同一个实例

三、单例模式的使用场景和实际的问题

1、饿汉模式和懒汉模式的区别

二者的区别是创建实例的时机,饿汉式在应用启动时就创建了实例,饿汉式是线程安全的,是绝对单例的。懒汉式在对外提供的获取方法被调用时会实例化对象。

饿汉模式的特点,加载类时比较慢,但运行时获取对象的速度比较快,是线程安全的。
懒汉模式的特点,加载类时比较快,但运行时获取对象的速度比较慢,是非线程安全的。

2、单例模式的常用使用场景

  • 配置文件的读取
  • 工具类的封装
  • 线程池的创建
  • 缓存的创建
  • 日志对象的创建
  • spring容器注册bean的时候默认使用的就是单例模式,可以通过@Scope注解去指定选择

3、为什么说懒汉模式的单例是非线程安全的?怎么去解决线程安全问题

问题分析:

在代码断点调式过程中发现可得知,在多线程的情况下,有一定的概率会出现一个单例类会有多个实例的情况。

解决方案:

(1)、方案1----对实例化方法加锁

 public static synchronized SingLeton2 getSingLeton2(){

        if (singLeton2 == null) {
            return new SingLeton2();
        }
        return singLeton2;
    }

方案一缺点:

加锁可以确保实例话方法只能有一个线程执行,确保两个线程不会同时进入实例化方法。缺点是同步锁的加锁和解锁比较消耗资源,而且synched关键字修饰static方法时锁的是整个class,对性能会有影响。

(2)、方案2----double check+volatile方案

public static SingLeton2 getSingLeton2(){

        if (singLeton2 == null) {
            synchronized (SingLeton2.class){
                if (singLeton2 == null){
                    /**
                     * 1、分配内存
                     * 2、初始化对象
                     * 3、设置SingLeton2类执行内存(将实例分配到堆内存中)
                     */
                    singLeton2 = new SingLeton2();
                }
            }
        }
        return singLeton2;
    }

double check方案可以实现线程安全,且降低内存消耗。代码中三行注释代表了new对象时底层进行了三步操作,由于JVM优化算法可能会指令重排,也就是第二步和第三步执行的顺序会互换。这样可能会出现第一个第一个线程出现了指令重排情况,还没来得及初始化对象,第二个线程就进行了对象获取,导致获取到的对象是null。
 

为避免这种情况的发生可以使用volatile关键字修饰静态类,禁止指令重排

private volatile static SingLeton2 singLeton2 = null; //声明类的实例,没有去实例化

(3)、方案3----也可以使用静态内部类的初始化延迟加载解决,静态内部类有初始化锁,达到线程安全的目的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值