深入理解Java设计模式——单例模式

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 深入理解Java设计模式——单例模式,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、什么是单例模式

在这里插入图片描述

1. 单例模式优缺点

优点:
1、单例类只有一个实例
2、共享资源,全局使用
3、节省创建时间,提高性能

缺点:可能存在线程不安全的问题

二、单例模式应用场景

windows的任务管理器、网站的计数器、Web应用配置对象的读取、多线程的线程池

三、单例的七种写法

分别是「饿汉」、「懒汉(非线程安全)」、「懒汉(线程安全)」、「双重校验锁」、「静态内部类」、「枚举」和「容器类管理、静态块初始化」

1. 饿汉式

package com.demo.singleton.v1;

/**
 * @Author: JYC
 * @Title: 饿汉式
 * @Description: TODO
 * @Date: 2022/4/20 10:11
 */
public class SingletonV1 {
    /**
     * 饿汉式
     * 优点:先天性线程安全,当类初始化的时候,就被创建该对象。
     * 缺点:如果项目中使用过多的饿汉式会发生问题,项目在启动的时候会变得非常慢,存放在方法区占用内存比较大
     * 如果用户不使用该对象的时候,也会被提前创建好
     */
    private static SingletonV1 singLetonV1 = new SingletonV1();

    // 1. 单例模式是否可以让程序员初始化
    private SingletonV1() {

    }

    /**
     * 返回该对象的实例
     * @return
     */
    public static SingletonV1 getSingLetonV1() {
        return singLetonV1;
    }
}

package com.demo.singleton.v1;

/**
 * @Author: JYC
 * @Title: SingletonV1Test
 * @Description: TODO
 * @Date: 2022/4/20 10:35
 */
public class SingletonV1Test {
    public static void main(String[] args) {
        SingletonV1 instance1 = SingletonV1.getSingLetonV1();
        SingletonV1 instance2 = SingletonV1.getSingLetonV1();
        System.out.println(instance1 == instance2); // 判断是否为同一个对象
    }

}

在这里插入图片描述

1.1 优缺点

饿汉式
优点:先天性线程是安全的,当类初始化的时候就会创建该对象。
缺点:如果饿汉式使用过多,可能会影响项目启动的效率问题。

2. 懒汉式(线程不安全)

package com.demo.singleton.v2;

/**
 * @Author: JYC
 * @Title: 懒汉式(线程不安全)
 * @Description: TODO
 * @Date: 2022/4/20 10:50
 */
public class SingletonV2 {
    // 懒汉式 当真正需要使用该对象的时候才会被初始化
    private static SingletonV2 singletonV2;

    // 1. 单例模式是否可以让程序员初始化
    private SingletonV2() {

    }

    /**
     * 线程安全问题
     * 在多线程情况下,可能会被初始化多次
     * @return
     */
    public static SingletonV2 getSingletonV2() {
        // 当第一次singletonV2 等于null情况才会被初始化
        if (singletonV2 == null) {
            try {
                Thread.sleep(2000);  //
            } catch (Exception e) {

            }
            singletonV2 = new SingletonV2();
        }
        return singletonV2;
    }
}

package com.demo.singleton.v2;

/**
 * @Author: JYC
 * @Title: 懒汉式,模拟多线程操作
 * @Description: TODO
 * @Date: 2022/4/20 10:54
 */
public class SingletonV2Test {
    public static void main(String[] args) {
//        SingletonV2 singleton1 = SingletonV2.getSingletonV2();
//        SingletonV2 singleton2 = SingletonV2.getSingletonV2();
//        System.out.println(singleton1 == singleton2); // 判断是否为同一个对象

        // 如何去模拟高并发情况下,懒汉式线程安全问题
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SingletonV2 singleton1 = SingletonV2.getSingletonV2();
                    System.out.println(Thread.currentThread().getName() + "," + singleton1);
                }
            }).start();
        }
    }
}

在这里插入图片描述

3. 懒汉式(线程安全)

package com.demo.singleton.v2;

/**
 * @Author: JYC
 * @Title: 懒汉式(线程安全)
 * @Description: TODO
 * @Date: 2022/4/20 10:50
 */
public class SingletonV2 {
    // 懒汉式 当真正需要使用该对象的时候才会被初始化
    private static SingletonV2 singletonV2;

    // 1. 单例模式是否可以让程序员初始化
    private SingletonV2() {

    }

    /**
     * 创建和获取实例时上锁,可以解决线程安全问题,但是执行效率非常低
     * @return
     */
    public synchronized static SingletonV2 getSingletonV2() {
        try {
            Thread.sleep(2000);  //
        } catch (Exception e) {

        }
        // 当第一次singletonV2 等于null情况才会被初始化
        if (singletonV2 == null) {
            singletonV2 = new SingletonV2();
        }
        return singletonV2;
    }
}

package com.demo.singleton.v2;

/**
 * @Author: JYC
 * @Title: 懒汉式,模拟多线程操作
 * @Description: TODO
 * @Date: 2022/4/20 10:54
 */
public class SingletonV2Test {
    public static void main(String[] args) {
//        SingletonV2 singleton1 = SingletonV2.getSingletonV2();
//        SingletonV2 singleton2 = SingletonV2.getSingletonV2();
//        System.out.println(singleton1 == singleton2); // 判断是否为同一个对象

        // 如何去模拟高并发情况下,懒汉式线程安全问题
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SingletonV2 singleton1 = SingletonV2.getSingletonV2();
                    System.out.println(Thread.currentThread().getName() + "," + singleton1);
                }
            }).start();
        }
    }
}

在这里插入图片描述

4. 双重检验锁(DCL)

package com.demo.singleton.v3;

/**
 * @Author: JYC
 * @Title: SingletonV3
 * @Description: TODO
 * @Date: 2022/4/20 11:33
 */
public class SingletonV3 {
    private static SingletonV3 singletonV3;
    // 双重检验锁,解决懒汉式读和写都加上锁的问题,缺点:第一次创建对象可能会比较慢

    private SingletonV3() {

    }

    /**
     * 读的不加锁,写的时候才会加锁
     */
    public static SingletonV3 getSingletonV3() {

        // 当多个线程同时在可能new对象的时候,才会加锁,保证线程安全问题
        if (singletonV3 == null) {
            try {
                Thread.sleep(2000);  //
            } catch (Exception e) {

            }
            synchronized (SingletonV3.class) {
                if (singletonV3 == null) { // 当前线程已经获取到锁了,再判断一下该对象是否已经初始化过,没有初始化过的创建
                    singletonV3 = new SingletonV3();
                }
            }
        }
        return singletonV3;
    }
    // 双重检验锁的目的时什么?    解决懒汉式获取对象效率问题。
}

package com.demo.singleton.v3;

/**
 * @Author: JYC
 * @Title: 模拟多线程操作
 * @Description: TODO
 * @Date: 2022/4/20 11:57
 */
public class SingletonV3Test {
    public static void main(String[] args) {
        // 如何去模拟高并发情况下,懒汉式线程安全问题
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SingletonV3 singleton1 = SingletonV3.getSingletonV3();
                    System.out.println(Thread.currentThread().getName() + "," + singleton1);
                }
            }).start();
        }
    }
}

在这里插入图片描述

5. 静态内部类形式

package com.demo.singleton.v4;

/**
 * @Author: JYC
 * @Title: SingletonV4
 * @Description: TODO
 * @Date: 2022/4/20 12:08
 */
public class SingletonV4 {
    private SingletonV4() {
        System.out.println("构造函数被初始化...");
    }

    public static SingletonV4 getInstance() {
        return SingletonV4Utils.singletonV4;
    }

    // 在类里面嵌套的
    private static class SingletonV4Utils {
        private static final SingletonV4 singletonV4 = new SingletonV4();

    }

    // 静态内部类特征:继承懒汉式和饿汉式优点,同时解决双重检验锁第一次加载慢的问题,读和写都不需要同步,效率非常高...
    public static void main(String[] args) {
        System.out.println("项目启动成功...");
        SingletonV4 instance1 = SingletonV4.getInstance();
        SingletonV4 instance2 = SingletonV4.getInstance();
        System.out.println(instance1 == instance2);
    }
}

在这里插入图片描述

6. 枚举形式

package com.demo.singleton.v7;

/**
 * @Author: JYC
 * @Title: EnumSingleton
 * @Description: TODO
 * @Date: 2022/4/20 14:29
 */
public enum EnumSingleton {
    INSTANCE;

    // 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破击
    public void add(){
        System.out.println("add方法...");
    }
}

package com.demo.singleton.v7;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @Author: JYC
 * @Title: EnumSingletonTest
 * @Description: TODO
 * @Date: 2022/4/20 14:29
 */
public class EnumSingletonTest {
    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        EnumSingleton singleton1 = EnumSingleton.INSTANCE;
        EnumSingleton singleton2 = EnumSingleton.INSTANCE;
        System.out.println(singleton1 == singleton2);
        singleton1.add();

        // 通过反射破坏单例
        Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        EnumSingleton singleton3 = declaredConstructor.newInstance();
        System.out.println(singleton3 == singleton1);
    }
}

在这里插入图片描述

7. 使用容器管理

8. 如何防止破坏单例

虽然单例通过私有构造函数,可以实现防止程序猿初始化对象,但是还可以通过反射和序列化技术破解单例。

8.1 使用反射技术破解单例

package com.demo.singleton.v5;

/**
 * @Author: JYC
 * @Title: SingletonV5
 * @Description: TODO
 * @Date: 2022/4/20 14:47
 */
public class SingletonV5 {
    private static SingletonV5 singletonV5;
    // 双重检验锁,解决懒汉式读和写都加上锁的问题,缺点:第一次创建对象可能会比较慢
    // 如何解决读和写都不加锁,还能保证唯一性线程安全问题

    private SingletonV5() {
        System.out.println("SingletonV5被初始化...");
    }

    /**
     * 读的不加锁,写的时候才会加锁
     */
    public static SingletonV5 getSingletonV5() {

        // 当多个线程同时在可能new对象的时候,才会加锁,保证线程安全问题
        if (singletonV5 == null) {
            try {
                Thread.sleep(2000);  //
            } catch (Exception e) {

            }
            synchronized (SingletonV5.class) {
                if (singletonV5 == null) { // 当前线程已经获取到锁了,再判断一下该对象是否已经初始化过,没有初始化过的创建
                    singletonV5 = new SingletonV5();
                }
            }
        }
        return singletonV5;
    }
    // 双重检验锁的目的时什么?    解决懒汉式获取对象效率问题。
}

package com.demo.singleton.v5;

import com.demo.singleton.v3.SingletonV3;

import java.lang.reflect.Constructor;

/**
 * @Author: JYC
 * @Title: SingletonV5Test
 * @Description: TODO
 * @Date: 2022/4/20 14:48
 */
public class SingletonV5Test {
    public static void main(String[] args) throws Exception {
        // 单例基本原则:保证在单个jvm中不重复创建
        SingletonV5 singleton1 = SingletonV5.getSingletonV5();
        // 如何去破解单例?  使用java的反射技术、序列化技术
        Constructor<SingletonV5> declaredConstructor = SingletonV5.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        // 使用java的反射技术创建
        SingletonV5 singleton2 = declaredConstructor.newInstance();
        System.out.println(singleton1 == singleton2);
    }
}

在这里插入图片描述

8.2 如何防止被反射破解

package com.demo.singleton.v5;

/**
 * @Author: JYC
 * @Title: SingletonV5
 * @Description: TODO
 * @Date: 2022/4/20 14:47
 */
public class SingletonV5 {
    private static SingletonV5 singletonV5;
    // 双重检验锁,解决懒汉式读和写都加上锁的问题,缺点:第一次创建对象可能会比较慢
    // 如何解决读和写都不加锁,还能保证唯一性线程安全问题

    private SingletonV5() throws Exception {
        if (singletonV5 != null) {
            throw new Exception("对象已经被初始化");
        }
        System.out.println("SingletonV5被初始化...");
    }

    /**
     * 读的不加锁,写的时候才会加锁
     */
    public static SingletonV5 getSingletonV5() throws Exception {

        // 当多个线程同时在可能new对象的时候,才会加锁,保证线程安全问题
        if (singletonV5 == null) {
            try {
                Thread.sleep(2000);  //
            } catch (Exception e) {

            }
            synchronized (SingletonV5.class) {
                if (singletonV5 == null) { // 当前线程已经获取到锁了,再判断一下该对象是否已经初始化过,没有初始化过的创建
                    singletonV5 = new SingletonV5();
                }
            }
        }
        return singletonV5;
    }
    // 双重检验锁的目的时什么?    解决懒汉式获取对象效率问题。
}

package com.demo.singleton.v5;

import com.demo.singleton.v3.SingletonV3;

import java.lang.reflect.Constructor;

/**
 * @Author: JYC
 * @Title: SingletonV5Test
 * @Description: TODO
 * @Date: 2022/4/20 14:48
 */
public class SingletonV5Test {
    public static void main(String[] args) throws Exception {
        // 单例基本原则:保证在单个jvm中不重复创建
        SingletonV5 singleton1 = SingletonV5.getSingletonV5();
        // 如何去破解单例?  使用java的反射技术、序列化技术
        Constructor<SingletonV5> declaredConstructor = SingletonV5.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        // 使用java的反射技术创建
        SingletonV5 singleton2 = declaredConstructor.newInstance();
        System.out.println(singleton1 == singleton2);
    }
}

在这里插入图片描述

8.3 使用序列化技术破解单例

package com.demo.singleton.v6;

import java.io.*;

/**
 * @Author: JYC
 * @Title: SingletonV6
 * @Description: TODO
 * @Date: 2022/4/20 14:20
 */
public class SingletonV6 implements Serializable {
    public static void main(String[] args) throws Exception {
        // 从硬盘中读取到内存——反序列化
        SingletonV6 singleton1 = SingletonV6.getSingLetonV6();
        FileOutputStream fos = new FileOutputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(singleton1);
        oos.flush();
        oos.close();

        // 反序列化
        FileInputStream fis = new FileInputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        SingletonV6 singleton2 = (SingletonV6) ois.readObject();
        System.out.println(singleton1 == singleton2);
    }

    /**
     * 饿汉式
     * 优点:先天性线程安全,当类初始化的时候,就被创建该对象。
     * 缺点:如果项目中使用过多的饿汉式会发生问题,项目在启动的时候会变得非常慢,存放在方法区占用内存比较大
     * 如果用户不使用该对象的时候,也会被提前创建好
     */
    private static SingletonV6 singLetonV6 = new SingletonV6();

    // 1. 单例模式是否可以让程序员初始化
    private SingletonV6() {

    }

    /**
     * 返回该对象的实例
     *
     * @return
     */
    public static SingletonV6 getSingLetonV6() {
        return singLetonV6;
    }
}

在这里插入图片描述
在这里插入图片描述

8.4 如何防止被序列化技术破解

package com.demo.singleton.v6;

import java.io.*;

/**
 * @Author: JYC
 * @Title: SingletonV6
 * @Description: TODO
 * @Date: 2022/4/20 14:20
 */
public class SingletonV6 implements Serializable {
    public static void main(String[] args) throws Exception {
        // 从硬盘中读取到内存——反序列化
        SingletonV6 singleton1 = SingletonV6.getSingLetonV6();
        FileOutputStream fos = new FileOutputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(singleton1);
        oos.flush();
        oos.close();

        // 反序列化
        FileInputStream fis = new FileInputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        SingletonV6 singleton2 = (SingletonV6) ois.readObject();
        System.out.println(singleton1 == singleton2);
    }

    /**
     * 饿汉式
     * 优点:先天性线程安全,当类初始化的时候,就被创建该对象。
     * 缺点:如果项目中使用过多的饿汉式会发生问题,项目在启动的时候会变得非常慢,存放在方法区占用内存比较大
     * 如果用户不使用该对象的时候,也会被提前创建好
     */
    private static SingletonV6 singLetonV6 = new SingletonV6();

    // 1. 单例模式是否可以让程序员初始化
    private SingletonV6() {

    }

    /**
     * 返回该对象的实例
     *
     * @return
     */
    public static SingletonV6 getSingLetonV6() {
        return singLetonV6;
    }

    // 返回序列化获取对象,保证为单例
    public Object readResolve() {
        return singLetonV6;
    }
}

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131276.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!