Java-设计模式之单例模式

前言

简述:以下为单例模式的使用,单例模式的使用方法有很多,可以根据项目自身情况进行选择,选择的设计模式适合自己的就是好的设计模式

一、饿汉式单例模式

/**
 * 饿汉式单例模式
 * </p>
 * 优点:简单方便,不用担心线程安全问题,保证对象只存在一个单例
 * 缺点:不管程序中是否使用该对象,都会产生实例对象,会降低应用的启动速度
 */
public class HungrySingleton {
    private static HungrySingleton singleton = new HungrySingleton();

    //构造方法私有化,确保不能被外部调用
    private HungrySingleton() {

    }

    //获取对象的唯一入口
    public static HungrySingleton getInstance() {
        return singleton;
    }

    public void println(Object o) {
        System.out.println("o = " + o);
    }
}

程序在加载的时候,对象就会变初始化,这也就是我们俗称的饿汉式

单例化主要是为了确保程序中改对象只有一个实例,检测代码如下

public class Main {
    public static void main(String[] args) {
        //检测对象是否唯一
        HungrySingleton s1 = HungrySingleton.getInstance();
        HungrySingleton s2 = HungrySingleton.getInstance();
        System.out.println("对象是否相等:" + (s1 == s2));
    }
}

输出结果:

对象是否相等:true

结论:对象只有一个

二、懒汉式单例

/**
 * 懒汉式单例化
 * </p>
 * 优点:对象的生成是在应用使用的时候才会去构造,可以提高应用的启动速度
 * 缺点:不是线程安全的,如果多个线程同时调用getInstance()方法,可能会产生多个单例对象
 */
public class LazySingleton {
    private static LazySingleton singleton = null;

    //构造方法私有化,确保不能被外部调用
    private LazySingleton() {

    }

    //获取对象的唯一入口
    public static LazySingleton getInstance() {
        if (singleton == null) {
            //此处模拟对象实例化要消耗的时间
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new LazySingleton();
        }
        return singleton;
    }

    public void println(Object o) {
        System.out.println("o = " + o);
    }
}

懒汉式单例不用随着程序启动就实例化,而是需要的时候调用getInstance()方法才会实例化,但是在多线程使用的情况下,无法保证实例唯一性

检测代码如下:

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                //通过Thread创建10个线程
                //直接输出对象地址
                System.out.println("对象地址 = " + LazySingleton.getInstance().toString());
            }).start();
        }
    }
}

输出结果:

对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@6e4b852a
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7079d68d
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@632a10ea
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@54437fbd
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@72679c71
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@79a2688b
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@6e4b852a
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@8ae2e7e
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@3354d998
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@48ab8b1a

结论:内存地址输出的结果各式各样,说明对象无法在多线程的情况下无法保证唯一性

三、懒汉式加锁单例

/**
 * 懒汉式加锁单例化
 * </p>
 * 优点:保证对象创建的时候不会多次实例化
 * 缺点:由于同步锁的原因,会导致获取对象的时候每次都要检查锁的状态,消耗性能
 */
public class LazySingleton {
    private static LazySingleton singleton = null;

    //构造方法私有化,确保不能被外部调用
    private LazySingleton() {

    }

    //获取对象的唯一入口
    public static synchronized LazySingleton getInstance() {
        if (singleton == null) {
            //此处模拟对象实例化要消耗的时间
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new LazySingleton();
        }
        return singleton;
    }

    public void println(Object o) {
        System.out.println("o = " + o);
    }
}

懒汉式加锁单例可以保证唯一性,但是会浪费过多性能,每次获取对象的等待时间可能会延长

检测代码如下:

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                //通过Thread创建10个线程
                //直接输出对象地址
                System.out.println("对象地址 = " + LazySingleton.getInstance().toString());
            }).start();
        }
    }
}

输出结果:

对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c
对象地址 = com.huluobox.designpatterns.singleton.LazySingleton@7245297c

结论:内存地都一样,说明对象是唯一的

四、懒汉式双重锁单例

/**
 * 懒汉式双重锁单例化
 * </p>
 * 优点:减少性能的浪费,需要的时候才进行实例化
 * 缺点:代码复杂了一点
 */
public class LazySingleton {
    //关于volatile关键字可以百度查看相关介绍
    private static volatile LazySingleton singleton = null;

    //构造方法私有化,确保不能被外部调用
    private LazySingleton() {

    }

    //获取对象的唯一入口
    public static LazySingleton getInstance() {
        //此处检查必须保留,负责和方法上加锁是一样的道理
        if (singleton == null) {
            synchronized (LazySingleton.class) {
                if (singleton == null) {
                    singleton = new LazySingleton();
                }
            }
        }
        return singleton;
    }

    public void println(Object o) {
        System.out.println("o = " + o);
    }
}

五、静态内部类单例化

/**
 * 内部静态类单例化
 * </p>
 * 优点:线程安全
 * 缺点:可能会被人为破坏,静态内部类如果添加多个单例对象,那只要有一个使用,其他的都会被初始化
 */
public class InsideSingleton {
    //构造方法私有化,确保不能被外部调用
    private InsideSingleton() {

    }
    /**
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
     */
    private static class SingletonHolder {
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static final InsideSingleton INSTANCE = new InsideSingleton();
    }



    public static InsideSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

这里就不演示了,需要注意上面说的缺点即可

六、枚举单例化

/**
 * 枚举单例化
 * </p>
 * 优点:唯一,线程安全,可以拒绝被反射
 * 缺点:暂时还没有
 */
public enum EnumSingleton {
    INSTANCE;

    public void println(Object o) {
        System.out.println(o);
    }
}

使用如下:

public class Main {
    public static void main(String[] args) {
        EnumSingleton.INSTANCE.println("hha");
    }
}

使用结语

以上列举了多种单例模式的写法,分析了其利弊之处。同时还介绍了目前最佳的单例写法——枚举模式(枚举单例思路主要来源于:Effective Java 这本书),单例模式介绍就到这了,如文中发现有误之处还希望大家指出,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值