设计模式之单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

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

注意:

1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例。

4、必须保证在⼀一个JVM中,该对象只有⼀一个实例例存在。

第一种写法:饿汉式,将实例类的构造方法设置成私有的,定义一个final修饰的成员对象,向外提供一个静态类,将成员对象返回回去。

这种写法的缺点:不管这个对象有没有用到都在类加载就完成实例,并且不能防止反序列化的情况。

package com.zcm.singleton;

/**
 * @program: DesignPatterns_20201119
 * @ClassName User01
 * @Description 运用场景:只须一个实例的,饿汉式:类加载到内存后就是想一个单例,JVM保证线程安全
 * * 缺点:不管用不用,类加载就完成实例化,不能让防止反序列化
 * @Author zcm
 * @Date 2020/11/19 12:57
 * @Version V1.0
 */
public class User01 {
    /**
     * 设为私有,防止有人引用
     */
    private static final User01 USER_01 = new User01();

    /**
     * 构造方法设为私有防止有人通过new的形式破坏单例
     */
    private User01() {
    }

    /**
     * @Description:提供一个静态方法可供方便他人调用这个单例对象
     */
    public static User01 getInstance() {
        return USER_01;
    }

    public static void main(String[] args) {
        User01 user01 = User01.getInstance();
        User01 user02 = User01.getInstance();
        System.out.println(user01 == user02);
    }
}

第二种:将构造方法设置为私有的,定义一个final修饰的并且私有的成员对象,利用静态区完成实例化,这种也是饿汉式的。

package com.singleton;

/**
 * @program: DesignPatterns_20201119
 * @ClassName User02
 * @Description:将构造方法设置为私有的,定义一个final修饰的并且私有的成员对象,利用静态区完成实例化,
 * @Author zcm
 * @Date 2020/11/19 13:02
 * @Version V1.0
 */
public class User02 {
    private static final User02 USER_02;

    static {
        USER_02 = new User02();
    }
    public static User02 getInstance() {
        return USER_02;
    }

    public static void main(String[] args) {
        User02 user01=User02.getInstance();
        User02 user02=User02.getInstance();
        System.out.println(user01==user02);
    }
}

第三种写法:将构造方法设置为私有的,定义一个成员变量,在需要用到对象在实例化,实例化之前加个判断,只有对象为空的情况下实例化,这种称为懒汉式,虽然达到了按需初始化的目的,但却带来线程不安全的问题。

package com.singleton;

/**
 * @program: DesignPatterns_20201119
 * @ClassName User03
 * @Description 懒汉式 单例实现,虽然达到了按需实例化,戴氏带来了更大的问题,线程不安全。
 * @Author zcm
 * @Date 2020/11/19 13:12
 * @Version V1.0
 */
public class User03 {
    private static User03 user03;

    private User03() {
    }

    public static User03 getInstance() {
        if (user03 == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            user03 = new User03();
        }
        return user03;
    }

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

}

第四种写法:为了解决第三种写法的线程不安全,我们通过synchronized方法加锁来实现线程安全。使用synchronized意味着降低执行效率。

package com.singleton;

/**
 * @program: DesignPatterns_20201119
 * @ClassName User04
 * @Description 懒汉式 单例实现,虽然达到了按需实例化,但是带来了更大的问题,线程不安全。
 * 通过synchronized 可以解决带来的线程不安全,但是使用synchronized意味着效率下降。
 * @Author zcm
 * @Date 2020/11/19 13:12
 * @Version V1.0
 */
public class User04 {
    private static User04 user04;

    private User04() {
    }
/**双重检验锁:效率⾼高;(解决问题:假如两个线程A、B,A执⾏了if (instance == null)语句句,它会认为单例例对象没有创建,此时线程切到B也 执⾏了同样的语句,B也认为单例例对象没有创建,然后两个线程依次执行同步代码块,并分别new了对象,导致不能双方拿到的不是同一个对象)*/

    public static  synchronized User04 getInstance() {
        if (user04 == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            user04 = new User04();
        }
        return user04;
    }

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

}

第五种写法:为了解决synchronized加锁带来的效率降低,我们将锁的范围缩小,众所周知,锁的范围越小,执行效率越高。但是缩小锁的范围会带来一个对象锁不住的情况。

package com.singleton;

/**
 * @program: DesignPatterns_20201119
 * @ClassName User05
 * @Description 为了执行效率,缩小锁的范围
 * @Author zcm
 * @Date 2020/11/20 13:08
 * @Version V1.0
 */
public class User05 {
    private static User05 user05;

    private User05() {
    }

    public static User05 getInstance() {
        if (user05 == null) {
            //为了执行效率,缩小锁的范围 你会发现锁不住
            synchronized (User05.class) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                user05 = new User05();
            }
        }
        return user05;
    }

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

}

第六种写法:为了解决第五种写法缩小锁的范围锁不住对象的情况,衍生出的第六种写法,加双重检查对象。这种写法也是被称之为现在最完美的写法之一。

package com.singleton;

/**
 * @program: DesignPatterns_20201119
 * @ClassName User05
 * @Description 为了执行效率,缩小锁的范围
 * @Author zcm
 * @Date 2020/11/20 13:08
 * @Version V1.0
 */
public class User06 {
    private static User06 user06;

    private User06() {
    }

    public static User06 getInstance() {
        if (user06 == null) {
            //为了执行效率,缩小锁的范围 你会发现锁不住 为了解决效率提升又只能是同一个对象的情况,那么双重检查
            synchronized (User06.class) {
                //双重检查  这个也是至今被称为完美的写法
                if (user06 == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    user06 = new User06();
                }
            }
        }
        return user06;
    }

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

}

第七种写法:静态内部类的方式,可以以利用JVM来保证单例,加载外部类的时候不会加载内部类,这样可以实现懒加载。

package com.singleton;

/**
 * @program: DesignPatterns_20201119
 * @ClassName User05
 * @Description 静态内部类方式
 * JVM保证单例
 * 加载外部类时不会加载内部类,这样可以实现懒加载
 * @Author zcm
 * @Date 2020/11/20 13:08
 * @Version V1.0
 */
public class User07 {

    private User07() {
    }

    private static class User07Holder {
        private static User07 user07 = new User07();

    }

    public static User07 getInstance() {
        return User07Holder.user07;
    }

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

}

第八种写法:利用枚举来达到单例的目的,这个是一位编程大佬,发现的新写法,被称之为最最最完美的写法。

package com.singleton;
/**
*@Description:一个java前辈发明的最完美写法
*枚举:使⽤用枚举除了了线程安全和防⽌止反射调⽤构造器之外,还提供了了⾃动序列化机制,防止反序列化的时候创建新的对象。
*@Author: zcm
*@Version:v.2.3.0
*@Date:2020/11/20 13:22
*/
public enum User08 {
    USER_08;

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

使用场景:

  1、某些类创建⽐比较频繁,对于⼀一些⼤大型的对象,这是⼀一笔很⼤大的系统开销。        

   2、省去了了new操作符,降低了了系统内存的使⽤用频率,减轻GC压⼒力力。          

 3、有些类如交易易所的核⼼心交易易引擎,控制着交易易流程,如果该类可以创建多个的话,系统完全乱了。,所以只有使⽤用单例例模式,才能保证核⼼心交易易服务器器独⽴立控制整个流程。 

总结:使用单例最关键考虑在多线程高并发下可能造成单例失效,而单例带来最大好处就是节省系统资源,减少I/O发生,解决类全局频繁的销毁重建。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值