侧边栏壁纸
博主头像
昂洋编程 博主等级

鸟随鸾凤飞腾远,人伴贤良品自高

  • 累计撰写 71 篇文章
  • 累计创建 79 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

单例模式

Administrator
2022-06-07 / 0 评论 / 0 点赞 / 34 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
本文最后更新于2024-06-14,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

饿汉式

核心是私有化构造方法、内部static final初始一个对象、对外提供获取实例的方法getInstance

/**
 * @author zhangmy
 * @date 2022/6/7 8:21
 * @description
 * 饿汉式
 * 类加载到内存后,就实例化一个单例,JVM保证线程安全(线程安全是指JVM每个class只会加载一次)
 * 简单实用,推荐使用
 * 唯一缺点:不管用到与否,类装载时就完成实例化
 */
public class Manager01 {

    private static final Manager01 INSTANCE = new Manager01();

    private Manager01(){}

    public static Manager01 getInstance() {
        return INSTANCE;
    }
}

饿汉式即不管是否使用,先初始化再说,那我们是否可以做到用到的时候再初始化呢,当然是可以的

懒汉式

package com.imysh.zmy.mca.designpattern.singleton;

/**
 * 懒汉式
 * 达到按需初始化的目的,但是带来线程不安全的问题
 * @author zhangmy
 * @date 2022/6/21 7:55
 * @description
 */
public class Manager02 {
    private static Manager02 INSTANCE;

    private Manager02(){}

    public static Manager02 getInstance() {
        if (INSTANCE == null) {
            // 这段try是为了测试多线程下 线程不安全的问题
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Manager02();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100 ; i++) {
            new Thread(() -> {
                System.out.println(Manager02.getInstance().hashCode());
            } ).start();
        }
    }
}

此种方式虽然达到按需初始化的目的,但是带来线程不安全的问题,就像示例中所示,如果线程sleep1毫秒,那么在多线的情况下肯定会存在线程不安全的问题,我们调用main方法看下hash值

image-1655943055831

很明显,并非是同一个对象,那么我们是否可以做优化,比如使用加锁的方式

package com.imysh.zmy.mca.designpattern.singleton;

/**
 * 懒汉式 - 加锁
 * 解决懒汉式线程不安全的问题,但是 同时带来效率降低的问题
 * @author zhangmy
 * @date 2022/6/21 7:55
 * @description
 */
public class Manager03 {
    private static Manager03 INSTANCE;

    private Manager03(){}

    /**
     * 此处 synchronized 锁定的是Manager03.class对象
     * @return
     */
    public static synchronized Manager03 getInstance() {
        if (INSTANCE == null) {
            // 这段try是为了测试多线程下 线程不安全的问题
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Manager03();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100 ; i++) {
            new Thread(() -> {
                System.out.println(Manager03.getInstance().hashCode());
            } ).start();
        }
    }
}

加上锁之后的确能解决线程安全的问题,但是很明显,只要在getInstance获取实例,都会校验,都会上锁,那么在多线程获取instance的时候明显效率肯定是降低的,那我们再尝试做个优化

package com.imysh.zmy.mca.designpattern.singleton;

/**
 * 懒汉式 - 部分加锁
 * 这种其实也没有解决线程不安全的问题
 * @author zhangmy
 * @date 2022/6/21 7:55
 * @description
 */
public class Manager04 {
    private static Manager04 INSTANCE;

    private Manager04(){}

    /**
     * 此处 synchronized 锁定的是Manager03.class对象
     * @return
     */
    public static Manager04 getInstance() {
        if (INSTANCE == null) {
            // 只针对特定的地方加锁
            synchronized (Manager04.class) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Manager04();
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100 ; i++) {
            new Thread(() -> {
                System.out.println(Manager04.getInstance().hashCode());
            } ).start();
        }
    }
}

这里呢我们只在创建实例的时候加锁,再已初始化的时候并不会发生锁竞争导致效率降低的问题,但是仔细观察,这种方式其实也没有解决线程安全的问题,在有两个线程都到锁那里进行竞争的时候都会初始化示例,调main方法试试

image-1655943511696

我们进一步优化

package com.imysh.zmy.mca.designpattern.singleton;

/**
 * 懒汉式 - 部分加锁 + 双重判断
 * 既解决线程安全,又相对提高效率
 * @author zhangmy
 * @date 2022/6/21 7:55
 * @description
 */
public class Manager05 {

    // volatile关键字 JIT优化
    private static volatile Manager05 INSTANCE;

    private Manager05(){}

    /**
     * 此处 synchronized 锁定的是Manager03.class对象
     * @return
     */
    public static Manager05 getInstance() {
        if (INSTANCE == null) {
            // 只针对特定的地方加锁
            synchronized (Manager05.class) {
                // 再判断一次
                if (INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Manager05();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100 ; i++) {
            new Thread(() -> {
                System.out.println(Manager05.getInstance().hashCode());
            } ).start();
        }
    }
}

这种双重判断的方式,就可以保证既解决线程安全,又相对提高效率,这也是懒汉式完美的写法,当然还有其他方式实现单例

静态内部类方式

package com.imysh.zmy.mca.designpattern.singleton;

/**
 * 静态内部类方式 -- 完美的方式
 * JVM保证线程安全(外部类只会加载一次,且内部类是用到的时候才会加载),即加载外部类时不会加载内部类
 *
 * @author zhangmy
 * @date 2022/6/21 7:55
 * @description
 */
public class Manager06 {

    private Manager06(){}

    /**
     * 使用静态内部类去初始化外部类
     */
    private static class Manager06Holder {
        private static final Manager06 INSTANCE = new Manager06();
    }

    private static Manager06 getInstance() {
        return Manager06Holder.INSTANCE;
    }
}

这也是比较的写法,代码也看上去简介,同时JVM保证线程安全(外部类只会加载一次,且内部类是用到的时候才会加载),即加载外部类时不会加载内部类。还有没有更加简介且完美的方式呢,当然有

枚举单例

package com.imysh.zmy.mca.designpattern.singleton;

/**
 * 枚举单例 -- 完美的写法
 * 不仅可以解决线程同步,还可以防止反序列化
 *
 * @author zhangmy
 * @date 2022/6/21 8:25
 * @description
 */
public enum  Manager07 {

    INSTANCE;


    public static void main(String[] args) {
        for (int i = 0; i < 100 ; i++) {
            System.out.println(Manager07.INSTANCE.hashCode());
        }
    }
}


枚举单例应该是最简单且最完美的方式了,但是看上去就不那么直观

总结

日常工作中,其实我们使用饿汉式就可以了,毕竟如果不需要使用,就没必要写这个类了吧

package com.imysh.zmy.mca.designpattern.singleton;

/**
 * @author zhangmy
 * @date 2022/6/7 8:21
 * @description
 * 饿汉式
 * 类加载到内存后,就实例化一个单例,JVM保证线程安全(线程安全是指JVM每个class只会加载一次)
 * 简单实用,推荐使用
 * 唯一缺点:不管用到与否,类装载时就完成实例化
 */
public class Manager01 {

    private static final Manager01 INSTANCE = new Manager01();

    private Manager01(){}

    public static Manager01 getInstance() {
        return INSTANCE;
    }
}

第二种懒汉式的完美写法也是可以

package com.imysh.zmy.mca.designpattern.singleton;

/**
 * 懒汉式 - 部分加锁 + 双重判断
 * 既解决线程安全,又相对提高效率
 * @author zhangmy
 * @date 2022/6/21 7:55
 * @description
 */
public class Manager05 {

    // volatile关键字 JIT优化
    private static volatile Manager05 INSTANCE;

    private Manager05(){}

    /**
     * 此处 synchronized 锁定的是Manager03.class对象
     * @return
     */
    public static Manager05 getInstance() {
        if (INSTANCE == null) {
            // 只针对特定的地方加锁
            synchronized (Manager05.class) {
                // 再判断一次
                if (INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Manager05();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100 ; i++) {
            new Thread(() -> {
                System.out.println(Manager05.getInstance().hashCode());
            } ).start();
        }
    }
}

第三种就是枚举方式

package com.imysh.zmy.mca.designpattern.singleton;

/**
 * 枚举单例 -- 完美的写法
 * 不仅可以解决线程同步,还可以防止反序列化
 *
 * @author zhangmy
 * @date 2022/6/21 8:25
 * @description
 */
public enum  Manager07 {

    INSTANCE;


    public static void main(String[] args) {
        for (int i = 0; i < 100 ; i++) {
            System.out.println(Manager07.INSTANCE.hashCode());
        }
    }
}


以上三种都是工作中可行的方式,可根据自己的需要和习惯采用何种方式

0

评论区