【设计模式】创建型-单例模式


一、单例模式

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

单例模式有三个要点:

  1. 某个类只能有一个实例
  2. 它必须自行创建这个实例
  3. 它必须自行向整个系统提供这个实例。

二、单例模式的八种实现方式

2.1、饿汉式(静态常量)

/*饿汉式(静态常量)*/
public class Singleton1 {
    //创建一个私有构造器,不让其他类new
    private Singleton1(){}
    //创建一个静态常量
    public static final Singleton1 INSTANCE = new Singleton1();
    //实例方法,方法是静态是为了通过类名调用
    public static Singleton1 newInstance(){
        return INSTANCE;
    }

    public static void main(String[] args) {
        Singleton1 s1 = Singleton1.newInstance();
        Singleton1 s2 = Singleton1.newInstance();
        //比较两个实例是否相等 结果:true
        System.out.println(s1==s2);
    }
}

优缺点:

  • 优点:简单,类加载的时候就完成了实例化,避免了线程安全问题。
  • 缺点:如果没用到这个实例,也会实例化,浪费了内存。

2.2、饿汉式(静态代码块)

/*饿汉式(静态代码块)*/
public class Singleton2 {
    //创建一个私有构造器,不让其他类new
    private Singleton2(){}
    //定义一个静态实例
    public static Singleton2 instance;
    //静态代码块中实例化对象
    static {
         instance= new Singleton2();
    }
    //提供一个公有静态方法,放回实例化对象
    public static Singleton2 newInstance(){
        return instance;
    }

    public static void main(String[] args) {
        Singleton2 s1 = Singleton2.newInstance();
        Singleton2 s2 = Singleton2.newInstance();
        //比较两个实例是否相等 结果:true
        System.out.println(s1==s2);
    }
}

优缺点跟上面的静态常量一样

2.3、懒汉式(线程不安全)

/*
 * 懒汉式
 * 实例是在使用的时候创建,但线程不安全,会创建多个对象
 * */
public class Singleton3 {
    //定义instance静态变量
    private static Singleton3 instance;
    private Singleton3(){}
    //初始化方法,实现懒加载,需要时才创建对象
    public static Singleton3 newInstance() throws InterruptedException {
        //没有实例,则创建对象
        if (instance == null){
            //让线程睡一下,创造多线程进入条件
            Thread.sleep(20);
            instance = new Singleton3();
        }
        //实例化过,直接返回
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //创建多线程,实现Runnable接口,重写run方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //通过哈希码,看对象是否一样
                        System.out.println(Singleton3.newInstance().hashCode());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

优缺点:

  • 优点:起到了懒加载效果,需要时才创建对象,但只适合在单线程下使用。
  • 缺点:在多线程情况下,一个线程 进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程又进来了,这时就产生了多个实例,造成线程不安全。

2.4、懒汉式(线程安全,同步方法)

/*
 * 懒汉式(线程安全,加入同步方法)
 * */
public class Singleton4 {
    //定义instance静态变量
    private static Singleton4 instance;
    private Singleton4(){}
	//加入同步方法,保证只有一个线程进入
    public static synchronized Singleton4 newInstance() throws InterruptedException {
        //没有实例,则创建对象
        if (instance == null){
            //让线程睡一下,创造多线程进入条件
            Thread.sleep(20);
            instance = new Singleton4();
        }
        //实例化过,直接返回
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //创建多线程,实现Runnable接口,重写run方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //通过哈希码,看对象是否一样
                        System.out.println(Singleton4.newInstance().hashCode());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

方式一是一个实例,同时锁住了空判断和创建实例,线程安全。但是这就相当于全部锁住了,就跟同步方法的效果一样,线程安全但效率很低

方式二不是一个实例,线程不安全,原因是一个线程进入了空判断,还没往下执行,另一个线程来了,其中一个线程拿到锁,往下执行创建了实例,执行完释放锁后,另一个线程也往下执行了并创建对象,两者创建的对象并不一致。

2.5、双重检查

public class Singleton6 {
    private static Singleton6 instance;
    private Singleton6(){};
    public static Singleton6 newInstance() throws InterruptedException {
        //双重检查,是单例
        if (instance == null){
            //首先判断实例是否为空,空就上锁
            synchronized (Singleton6.class){
                //上锁后,如果上面new出了个对象,此时在这判断是否为空,不为空就直接返回了,确保了只有一个实例
                if (instance == null){
                    Thread.sleep(20);
                    instance = new Singleton6();
                }
            }
        }
        return instance;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Singleton6.newInstance().hashCode());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

双重检查实际上就是在懒汉式(同步代码块)的内部再添加了一个判断,这样就保证线程安全

在这里插入图片描述

2.6、静态内部类

public class Singleton7 {
    private Singleton7() {}
    //静态内部类里实例化对象,在Singleton7加载的时候,SingletonInstance内部类不加载,只在实例的时候加载
    private static class SingletonInstance{
    //静态属性,实例化对象
        private static final Singleton7 INSTANCE = new Singleton7();
    }
    //提供一个静态的公有方法,返回SingletonInstance类的实例
    public static Singleton7 newInstance(){
        return SingletonInstance.INSTANCE;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(Singleton7.newInstance().hashCode());
            }).start();
        }
    }
}

这种方式采用了类加载的机制来保证初始化实例时只有一个线程,线程安全。静态内部类在 Singleton7 类被加载时并不会立即实例化,而是在调用 newInstance方法的时候才会实例化静态内部类,通过SingletonInstance类调用实例,从而完成 Singleton 的实例化。类的静态属性只会在第一次加载类的时候初始化,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

2.7、枚举

package com.s.singleton;
/**
 * 枚举
 */
public enum  Singleton8 {
    INSTANCE;
    public static void main(String[] args) {
        for (int i = 0; i < 100 ; i++) {
            new Thread(()->
                    System.out.println(Singleton8.INSTANCE.hashCode())).start();
        }
    }
}

枚举实现是单例的,线程安全,不仅可以解决线程同步,还可以防止反序列化。《Effective Java》作者 Josh Bloch 提倡的方式。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值