Android必备回顾:4、单例模式深究

目录

应用场景

01、饿汉式【线程安全-最常见】

02、饿汉式-写法2【线程安全】

03、懒汉式【线程不安全】

04、懒汉式【线程安全】

05、懒汉式【线程不安全】

06、双重检验锁【线程安全】---volatile 

07、静态内部类方式【线程安全】

08、枚举的方式【线程安全,防止反序列化】


应用场景

只需要一个实例

单例模式是指不允许用户调构造方法自己创建,通过静态方法获取对象,且获取的是同一个实例。

工作中用什么样的方式解决实际问题,不追求不必要的完美

主要分为:饿汉式、懒汉式、双重校验锁的方式、静态内部类的方式、枚举的方式

严格来讲,它有八种写法,只有两种是完美无缺的!

01、饿汉式【线程安全-最常见】

package com.yyh.singleton;

/**
 * 饿汉式【线程安全-最常见】
 * 类加载到内存后,就实例化一个对象,JVM保证线程安全
 * 简单实用,推荐使用!
 * 唯一缺点:不管用到与否,类装载时就完成实例化
 * (话说你不用它,你装载它干啥)
 */
public class Mgr01 {
    private static final Mgr01 INSTANCE = new Mgr01();

    private Mgr01(){}

    public static Mgr01 getInstance(){
        return INSTANCE;
    }

    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args){
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1 == m2);
    }

}

m1==m2 说明这两个的引用是指向的一个对象,即只有一个实例。

private static final Mgr01 INSTANCE = new Mgr01();
不管用到与否,类装载时就完成实例化!

能不能把创建的这个过程放在一个方法里需要的时候再创建,也就是用到再实例化,

于是就有了懒汉式的写法:03、懒汉式【线程不安全】

02、饿汉式-写法2【线程安全】

package com.yyh.singleton;

/**
 * 饿汉式-写法2【线程安全】
 * 跟01是一个意思,写法有点不同
 */
public class Mgr02 {
    private static final Mgr02 INSTANCE;
    static{
        INSTANCE = new Mgr02();
    }

    private Mgr02(){}

    public static Mgr02 getInstance(){
        return INSTANCE;
    }

    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args){
        Mgr02 m1 = Mgr02.getInstance();
        Mgr02 m2 = Mgr02.getInstance();
        System.out.println(m1 == m2);
    }

}

03、懒汉式【线程不安全】

同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。

添加代码睡眠一秒的目的,即线程之间运行,被其他线程打断的机会就增加了,更容易发现懒汉式的问题。

final修饰必须初始化,所以final加不了

package com.yyh.singleton;

/**
 * lazy loading 也称懒汉式【线程不安全】
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 */
public class Mgr03 {
    //final修饰必须初始化,所以final加不了
    //private final static Mgr03 INSTANCE;
    private static Mgr03 INSTANCE;

    private Mgr03(){}

    public static Mgr03 getInstance(){
        if(INSTANCE == null){
            try{
                //添加代码睡眠1秒的目的:
                //即线程之间运行,被其他线程打断的机会就增加了,更容易发现懒汉式的问题。
                Thread.sleep(1);
            }catch (Exception e){
                e.printStackTrace();
            }

            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }

    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args){
        for(int i=0; i<100; i++){
            new Thread(()->{
                //同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
                System.out.println(Mgr03.getInstance().hashCode());
            }).start();
        }
    }

}

多线程下发现会出现不同对象,达不到单线程的效果。分析如下:

步骤1:一个线程A,进入if(INSTANCE == null)以后暂停,

步骤2:线程B进入if(INSTANCE == null),new了一个对象以后,线程B暂停

步骤3:这时候线程A继续执行,又new了一对象

这时候就有两个对象了!

04、懒汉式【线程安全】

package com.yyh.singleton;

/**
 * lazy loading 也称懒汉式【线程安全】
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 * 可以通过 synchronized 解决,但也带来效率下降
 */
public class Mgr04 {
    //final修饰必须初始化,所以final加不了
    //private final static Mgr04 INSTANCE;
    private static Mgr04 INSTANCE;

    private Mgr04(){}

    //锁定当前对象
    public static synchronized Mgr04 getInstance(){
        //业务代码
        //业务代码
        if(INSTANCE == null){
            try{
                //添加代码睡眠1秒的目的:
                //即线程之间运行,被其他线程打断的机会就增加了,更容易发现懒汉式的问题。
                Thread.sleep(1);
            }catch (Exception e){
                e.printStackTrace();
            }

            INSTANCE = new Mgr04();
        }
        return INSTANCE;
    }

    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args){
        for(int i=0; i<100; i++){
            new Thread(()->{
                //同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
                System.out.println(Mgr04.getInstance().hashCode());
            }).start();
        }
    }

}

但是这种直接把整个方法全锁了,这种锁太粗了,我的 "业务代码" 也执行不了,

在锁的优化中,有一种就是锁细化。

05、懒汉式【线程不安全】

package com.yyh.singleton;

/**
 * lazy loading 也称懒汉式【线程不安全】
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 * 可以通过 synchronized 解决,但也带来效率下降
 *
 * 加锁的同时,如何提高效率?
 * 不在整个方法上加锁,在方法内部需要的时候加锁
 * 多线程访问的时候,会出现线程安全问题
 */
public class Mgr05 {
    //final修饰必须初始化,所以final加不了
    //private final static Mgr04 INSTANCE;
    private static Mgr05 INSTANCE;

    private Mgr05(){}

    //锁定当前对象
    //public static synchronized Mgr05 getInstance(){
    public static Mgr05 getInstance(){
        //业务代码
        //业务代码
        if(INSTANCE == null){
            //妄图通过减少同步代码块的方式提高效率,然而不可行
            synchronized (Mgr05.class){
                try{
                    //添加代码睡眠1秒的目的:
                    //即线程之间运行,被其他线程打断的机会就增加了,更容易发现懒汉式的问题。
                    Thread.sleep(1);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            INSTANCE = new Mgr05();
        }
        return INSTANCE;
    }

    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args){
        for(int i=0; i<100; i++){
            new Thread(()->{
                //同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
                System.out.println(Mgr05.getInstance().hashCode());
            }).start();
        }
    }

}

多线程下发现会出现不同对象,达不到单线程的效果。分析如下:

步骤1:一个线程A,进入if(INSTANCE == null)以后暂停,

步骤2:线程B进入if(INSTANCE == null),加锁,new了一个对象以后,解锁

步骤3:由于线程B解锁了,这时候线程A继续执行,加锁,又new了一对象,

这时候就有两个对象了!

06、双重检验锁【线程安全】---volatile 

package com.yyh.singleton;

/**
 * lazy loading 也称懒汉式【线程安全】
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 * 可以通过 synchronized 解决,但也带来效率下降
 *
 * 加锁的同时,如何提高效率?
 * 不在整个方法上加锁,在方法内部需要的时候加锁
 * 多线程访问的时候,会出现线程安全问题
 *
 *  怎么解决?有人想出------->双重检验锁【线程安全】
 */
public class Mgr06 {
    //final修饰必须初始化,所以final加不了
    //private final static Mgr06 INSTANCE;

    //private static Mgr06 INSTANCE;
    //volatile表示禁止指令重排序。出现几率极少,几百万分之一
    private static volatile Mgr06 INSTANCE;

    private Mgr06(){}

    //锁定当前对象
    //public static synchronized Mgr05 getInstance(){
    public static Mgr06 getInstance(){
        if(INSTANCE == null){//Double check lock 【DCL】
            //妄图通过减少同步代码块的方式提高效率,然而不可行
            synchronized (Mgr06.class){
                if(INSTANCE == null){//双重检查
                    try{
                        //添加代码睡眠1秒的目的:
                        //即线程之间运行,被其他线程打断的机会就增加了,更容易发现懒汉式的问题。
                        Thread.sleep(1);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }

    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args){
        for(int i=0; i<100; i++){
            new Thread(()->{
                //同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
                System.out.println(Mgr06.getInstance().hashCode());
            }).start();
        }
    }

}

 真正 new 的过程,是由上图几步构成的,主要是指下面三步:

(1)new指令:在堆中开辟一块内存。成员变量 m 的值设为0

(2)invokespecial指令:调用构造方法。把初始化 m 的值变成8

(3)astore指令:建立关联。对象变量指向这块内存

指令重排,astore指令建立关联,线程得到的 m 的值为0,得到的是半初始化的值

 

 

07、静态内部类方式【线程安全】

package com.yyh.singleton;

/**
 * 静态内部类方式【线程安全】
 * JVM保证单例
 * 加载外部类时不会加载内部类,这样可以实现懒加载
 */
public class Mgr07 {
    private Mgr07(){}

    private static class Mgr07Holder{
        private final static Mgr07 INSTANCE = new Mgr07();
    }

    public static Mgr07 getInstance(){
        return Mgr07Holder.INSTANCE;
    }

    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args){
        for(int i=0; i<100; i++){
            new Thread(()->{
                //同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
                System.out.println(Mgr07.getInstance().hashCode());
            }).start();
        }
    }

}

08、枚举的方式【线程安全,防止反序列化】

package com.yyh.singleton;

/**
 *  枚举的方式【线程安全,防止反序列化】
 * 不仅可以解决线程同步,还可以防止反序列化问题
 */
public enum Mgr08 {

    INSTANCE;

    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args){
        for(int i=0; i<100; i++){
            new Thread(()->{
                //同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
                System.out.println(Mgr08.INSTANCE.hashCode());
            }).start();
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

被开发耽误的大厨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值