单例模式Singleton(个人笔记)

Singleton单例模式

23种经典设计模式之一。后续我会继续更新设计模式(看心情)。设计模式,是前人也就是之前的大佬一步一步一个坑踩出来得到的经验。这里不仅仅是java 也是很多种语言可以共同使用的一种思想。好了废话不多说这就上笔记。

只需要一个实例,严格意义上有八种写法

首先构造方法设成

私有的private ,其他类调用的时候new不出来。

public class Singleton01 {
    private static final Singleton01 INSTANCE = new Singleton01();

    private Singleton01(){}

    public static Singleton01 getInstance(){
        return INSTANCE;
    }

    public void m(){ System.out.println("m"); }

    public static void main(String[] args) {
        Singleton01 m1 = Singleton01.getInstance();
        Singleton01 m2 = Singleton01.getInstance();
        System.out.println(m1 == m2);
    }
}

饿汉式

类加载到内存后,就实例化一个单例,JVM保证线程安全。非常简单实用,推荐使用

唯一缺点:不管是否用到,都会加载的时候实例话。

实际上你要加载类的话是这样写的

Class.forname("类名称");

就是只会加载到内存并不会实例化。类加载时候,只要是被static关键字修饰的都会实例化。(不懂为啥的可以再看一遍static关键字作用)

public class Singleton02 {
    private static final Singleton02 INSTANCE;
    static {
        INSTANCE = new Singleton02();
    }
    private Singleton02(){}

    public static Singleton02 getInstance(){
        return INSTANCE;
    }

    public static void main(String[] args) {
        Singleton02 m1 = Singleton02.getInstance();
        Singleton02 m2 = Singleton02.getInstance();
        System.out.println(m1 == m2);
    }
}

本质上和第一种没有区别 只是改成了静态块的一种写法

import java.util.Objects;
public class Singleton03 {
    private static  Singleton03 INSTANCE;
    private Singleton03(){}
    public static Singleton03 getInstance(){
        if (Objects.isNull(INSTANCE)){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Singleton03();
        }
        return INSTANCE;
    }

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

lazy loading

懒汉式(什么时候我用 什么时候我初始化)

虽然达到了按需初始化的目的,但也带来了线程不安全的问题。

比如当第一个线程走到try-catch的时候 还没有被初始化 不知名的原因 这里用睡眠代替。

第二个线程也进来了,因为第一个线程的睡眠导致还是这个对象还是空。所以也进去了睡眠。

后面的线程也是。直到第一个线程把对象new出来过后。后面的进程才会读取到。

线程写法为 jdk1.8新增的 Lambda(那木大)表达式写法 不懂都可以去看看jdk每个版本新增的不同内容

顺便提一句Lambda函数式编程也是趋势所为。很多学习过面向函数式编程的小伙伴也知道。有很多语言是面向函数式的,比如python,golang等等,所以很多语言是学通一门学习学其他的语言的时候都会比较轻松。语言是相通的。

运行结果为

 因为线程太快的原因 所以睡眠期间进入过多少他就会改变多少次 表现为hashcode值不同 (但是hashcode值相同 对象也可能不一样)但是最后一个睡眠进程过了new出来过后,后面就是全部一样的。怎么样解决呢。

public static synchronized Singleton03 getInstance(){
    if (Objects.isNull(INSTANCE)){
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        INSTANCE = new Singleton03();
    }
    return INSTANCE;
}

很简单,加一个synchronized锁也是第四种写法,你会发现hashcode就全部都一样了,但这样效率也会降低。相信大部分人都不知道这个单词怎么读,只知道是什么意思,有什么用。如果你会读那么你是牛逼的。

第五种写法也是懒汉式

import java.util.Objects;
public class Singleton04 {
    private static Singleton04 INSTANCE;

    private Singleton04(){}

    public static  Singleton04 getInstance(){
        if (Objects.isNull(INSTANCE)){
            synchronized(Singleton04.class){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Singleton04();
            }
        }
        return INSTANCE;
    }

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

synchronized加在方法内 企图通过减小同步代码块的方式提高效率,然后发现不可行。同第三种一样

所以也就衍生出第六种写法

import java.util.Objects;
public class Singleton06 {
    private static Singleton06 INSTANCE;
    private Singleton06(){}
    public static Singleton06 getInstance(){
        if (Objects.isNull(INSTANCE)){
            synchronized(Singleton06.class){
                if (INSTANCE == null){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Singleton06();
                }
            }
        }
        return INSTANCE;
    }
    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            new Thread(()->{
                System.out.println(Singleton06.getInstance().hashCode());
            }).start();
        }
    }
}

以前的双重检查,写法也越来越复杂。但这种也可以解决问题。所以 以前的单例模式写法里面这种也是称为最完美的单例模式之一。有些人可能会觉得第一个判断为空没有必要,但实际可以减少synchronized加锁的次数

第七种也是完美的单例模式之一

public class Singleton07 {
    private Singleton07(){}

    private static class Singleton07Holder{
        private final static Singleton07 INSTANCE = new Singleton07();
    }

    public static Singleton07 getInstance(){
        return Singleton07Holder.INSTANCE;
    }

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

但是最后一种绝对会惊呼一些常人理解的完美单例之一。

public enum  Singleton08 {
    INSTANCE;

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

很简单。

这种写法既可以防止反序列化,也可以解决线程同步

是一本《effective java 》上来的方法,有时间读书的小伙伴可以去看看,作者是Joshua,是java设计师也是google的首席java架构师

以上也是我的学习笔记,顺便提一句 笔记使用有道云笔记 特别方便 手机也可以看。换台电脑也可以继续写笔记 。好像还有网页版吧,也不清楚。我一般都下的软件使用。感兴趣小伙伴也可以看看,我们下次再见,拜拜~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值