并发编程-单例

x33g5p2x  于2022-07-04 转载在 其他  
字(3.6k)|赞(0)|评价(0)|浏览(192)

并发编程-单例

说在前面的话

今天的文章很短,但是很经典,值得你仔细阅读每一个文字…

正如我开篇所说,我们要整理一些java并发编程的学习文档,这一篇就是第八篇:单例模式。这一篇主要聊聊单例的几种实现方式已经适用的场景。

开整

稍微理解一下

专业解释:单例就是确保一个类只有一个实例,并且有一个全局获取这个实例的访问点。

简单的说呢就是一个类只能创建一个实例,任何时候使用这个类的实例对象都是同一个。

基本都是了减少这个类对象创建和销毁过程的消耗。

嗯!

思考思考,一个类如果只有一个实例,必然不能随便创建,所以单例类的关键代码就是构造方法是私有的,不允许在其他地方随便创建。

单例的优点非常明确:因为只有一个实例,所以减少了内存的开销,尤其是创建和销毁的时候减少了资源浪费。避免了对资源的多重占用。

单例的缺点呢:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

单例的使用场景

  • 连接池:一个系统中一个数据源的连接池往往都是一个实例。
  • 线程池:同上。
  • 计数器:不用每次刷新都在数据库里加一次,用单例先缓存起来
  • 创建一次消耗的资源过多的类对象。

单例的实现

单例有7种实现方式

方式1:线程不安全的懒汉模式

直接上菜:

  • 构造方法私有化
  • 提供静态的私有的本类实例对象作为成员变量
  • 提供公共的静态的获取本类对象的方法
/**
 * @author 戴着假发的程序员
 */
public class Singleton {
    // 构造方法私有化
    private Singleton(){}
    // 私有静态当前类对象作为成员变量
    private static Singleton instance;
    // 静态的公共的可以获取当前类实例对象的方法
    public static Singleton getInstance(){
        // 判断instance是否存在,如果存在就直接返回,如果不存在就创建
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

上面的程序非常简单:如果要获取Singleton类的实例对象可以这样写Singleton.getInstance()

你会发现:

Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);// true 额。。。。。多执行几次,有可能也会是false

说明:

  • 这个方式一开始并没有创建instance,在第一次使用的时候才会创建,这才是懒汉模式的真谛。有效的实现了延迟加载。节省了内存。

  • 这个方式不是线程安全的,可能会有隐患。

  • 如果线程A调用getInstance方法,判断了instance为null,正准备创建对象,结果CPU不给资源了,于是就稍微停了一会,这是线程B也调用了getInstance方法,结果判断instance依然是null,于是就创建了instance对象。之后线程A开始执行,线程A不会再判断了,直接创建instance对象,这样的话这个类的对象就被创建了两次。

方式2:线程安全的懒汉模式

上菜:

  • 构造方法私有化
  • 提供静态的私有的本类实例对象作为成员变量
  • 提供公共的静态的同步的获取本类对象的方法
/**
 * @author 戴着假发的程序员
 */
public class Singleton1 {
    // 构造方法私有化
    private Singleton1(){}
    // 私有静态当前类对象作为成员变量
    private static Singleton1 instance;
    // 静态的公共的可以获取当前类实例对象的方法
    public static synchronized Singleton1 getInstance(){
        // 判断instance是否存在,如果存在就直接返回,如果不存在就创建
        if(instance == null){
            instance = new Singleton1();
        }
        return instance;
    }
}

这个测试吗很简单:

Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);// true 额。。。这里无论执行多少次都是true

说明:

  • 这种方式很明显就是在getInstance的方法上增加synchronized修饰符,这样就成功避免了线程安全的问题。
  • 但是我们都知道一旦加锁了,程序效率就会略有降低(不过呢,在JDK1.6之后synchronized做了优化,如果没有大量并发的情况下其实效率影响也不大)
方式3:饿汉模式

饿汉模式关键在这个“饿”字。额… 记得以前我吃饭吃的很着急的时候,我亲爱的老母亲就说:“慢点吃,搞得饿死鬼上身似得”。非常庆幸的是老母亲现在也会这样说我。哈哈哈哈哈。。。。

言归正传:饿汉模式就是在一开始的时候直接创建实例对象

  • 构造方法私有化
  • 提供静态的私有的本类实例对象作为成员变量,并且直接实例化
  • 提供公共的静态的获取本类对象的方法
/**
 * @author 戴着假发的程序员
 */
public class Singleton2 {
    // 构造方法私有化
    private Singleton2(){}
    // 私有静态当前类对象作为成员变量
    private static Singleton2 instance = new Singleton2();
    // 静态的公共的可以获取当前类实例对象的方法
    public static synchronized Singleton2 getInstance(){
        // 判断instance是否存在,如果存在就直接返回,如果不存在就创建
        if(instance == null){
            instance = new Singleton2();
        }
        return instance;
    }
}

饿汉模式真的有点饿,一开始就创建了。

但是如果,现在不饿呢,直接把饭煮了是不是会有点浪费。

所以饿汉模式的问题就是如果一开始就把类对象创建好了, 但是这个对象长时间用不到,那么就是有些浪费资源了。

所以呀,还是要看实际的使用场景的。

方式4:双检锁/双重校验锁

程序,必须追求效率和省资源。所以我们希望单利模式中创建对象的方法也是效率高高滴。

但是我们一旦给方法加锁必然降低方法的执行效率,所以双重校验锁可能是一个不错的选择,既能能相对的提高程序效率,又能保证线程安全。

具体的实现就是:

/**
 * @author 戴着假发的程序员
 */
public class Singleton3 {
    // 构造方法私有化
    private Singleton3(){}
    // 私有静态当前类对象作为成员变量
    private static Singleton3 instance;
    // 静态的公共的可以获取当前类实例对象的方法
    public static Singleton3 getInstance(){
        // 判断instance是否存在,如果存在就直接返回,如果不存在就创建
        if (instance==null) {
            // 上锁
            synchronized (Singleton3.class) {
                // 再次判断instance是否存在,如果存在就直接返回,如果不存在就创建
                if (instance == null) {
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}
方式5:静态内部类

静态内部类的实现方式也叫登记式。是个啥情况呢:

直接上菜:

/**
 * @author 戴着假发的程序员
 */
public class Singleton4 {
    // 构造方法私有化
    private Singleton4(){}
    // 准备一个静态内部类
    private static class SingletonHolder{
        // 申明并且实例化一个外部单利类的实例对象,并且设置为常量。
        private final static Singleton4 INSTANCE = new Singleton4();
    }
    // 静态的公共的可以获取当前类实例对象的方法
    public static Singleton4 getInstance(){
        // 直接返回内部类的成员常量
        return SingletonHolder.INSTANCE;
    }
}

这种方式有啥用?

  • 在没有调用getInstance方法之前,静态内部了会延迟加载也就是对象的创建会延迟。
  • 利用classloader的机制确保了在创建实例是肯定是单线程的。
  • 当然了如果你不希望实例对象延迟加载那还是使用饿汉模式比较好。
方式6:枚举

这个方式实在太简单,也好理解(当然你首先要知道啥是枚举)。所以就不多做解释了。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

关于单利模式的情况就是这样了。

还有其他的并发编程相关的内容,我会持续更新,欢迎关注。

我是”起点编程“的"戴着假发的程序员" 欢迎关注…欢迎评论。。。。。

起点编程-是你我的未来…

相关文章