Java之单例模式

这篇具有很好参考价值的文章主要介绍了Java之单例模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一.上节内容

1.什么是线程安全

2.线程不安全的原因

3.JMM(Java内存模型)

4.synchronized锁

5.锁对象

6.volatile关键字

7.wait()和notify()

8.Java中线程安全的类

二.单例模式

1.什么是单例

2.怎么设计一个单例

1.口头约定

2.使用编程语言的特性

三.饿汉模式

四.懒汉模式

1.单线程下的懒汉模式

2.多线程下的懒汉模式

3.分析线程不安全的原因

4.加锁解决这个问题

5.双重检查锁

6.加volatile优化(单例懒汉模式最终版)


一.上节内容

上节内容指路:Java之线程安全

1.什么是线程安全

在多线程环境下程序运行的结果与单线程环境下程序运行的结果不一样(不达预期)

2.线程不安全的原因

1.多个线程对同一个共享变量做修改

2.线程是抢占式执行的,CPU的调度是随机的

3.原子性:代码没有一次性的执行完毕

4.内存可见性:线程1修改了共享变量的值,线程2不能及时获取到最新的值

5.有序性:由于指令重排序,指令无法按照书写的顺序进行执行.

3.JMM(Java内存模型)

JMM可以解决原子性,内存可见性,有序性的问题

Java之单例模式

1.主内存,进程启动时在主内存中申请内存资源,也就是内存条,用来保存所有的变量

2.工作内存,对应CPU中的缓存,每个线程都有自己的工作内存,工作内存互不影响,线程之间起到了内存隔离的效果

3.JMM规定,一个线程更新共享变量值的时候,必须先从主内存中加载到变量值到对应线程的工作内存中,修改完成后再将工作内存中的值刷新到主内存中.

4.synchronized锁

1.加锁之后可以将多线程(并发执行)变成单线程(串行执行).   ---解决了原子性

2.由于是单线程执行,线程2读到的值一定是线程1修改过的值  ---解决了内存可见性

3.不能解决有序性

synchronized的使用

1.可以修饰普通的方法  ---锁对象是new出来的对象(当前对象,this)

2.可以修饰静态的方法  ---锁对象是类对象,类名.class

3.可以修饰代码块          ---锁对象是指定的

5.锁对象

1.任何对象都可以充当锁对象,类对象,普通对象

2.锁对象的对象头中的markword区域记录当前争抢到锁的线程信息

3.多线程环境下判断是否发生锁竞争,只需要判断是否争抢的是一把锁.

6.volatile关键字

1.可以解决内存可见性      MESI缓存一致性协议和内存屏障

2.可以解决有序性

3.不能解决原子性

4.只能修饰变量

原子性 可见性 有序性
synchronized Y Y N
volatile N Y Y

7.wait()和notify()

1.wait()是让线程进入休眠状态,notify()是唤醒等待的线程,都是Object中的类

2.wait()和notify()都会释放锁资源,wait()和notify()要对于同一个锁搭配使用.

 面试题:说一下wait()和sleep()的区别

1.本质上都是让线程等待,但是两个方法没什么关系

2.wait()是Object类中定义的方法,sleep()是Thread类中定义的方法

3.wait()必须与synchronized搭配使用,调用之后会释放锁.sleep()只是让线程进入堵塞等待,和锁没有什么区别

8.Java中线程安全的类

线程安全的类:Vector (不推荐使用)  Stack  HashTable (不推荐使用)  ConcurrentHashMap
StringBuffer

线程不安全的类:ArrayList   LinkedList   HashMap   TreeMap   HashSet   TreeSet   StringBuilder

二.单例模式

1.什么是单例

单例模式是一种设计模式(设计模式:就是在特定的场景下,解决问题最优的方式,类似于棋谱),单例:顾名思义,全局只有一个实例对象

2.怎么设计一个单例

1.口头约定

对外提供一个方法,规定使用者只能通过这个方法来获取这个类的实例对象(不靠谱,有些人就是new出来对象,不采用你这个方法获取对象)

2.使用编程语言的特性

Java中哪些对象时全局唯一的?

.class对象,比如Object.class   类对象

使用static修饰的属性,所有的实例对象访问的都是同一个类属性.

因此我们可以通过类对象和static配合的方式实现单例

实现单例有两种模式,饿汉模式和懒汉模式

三.饿汉模式

类的加载就完成初始化

public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return instance;
    }
}

优点:书写简单,不容易出错

四.懒汉模式

1.单线程下的懒汉模式

避免程序启动的时候浪费过多的系统资源,当程序使用这个对象的时候再对这个对象进行初始化

public class SingletonLazy {
    private static SingletonLazy instance = null;

    private SingletonLazy() {

    }

    public static SingletonLazy getInstance() {
        //在获取单例对象的时候,判断是否已经被创建,没有创建则创建
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

其实也不难写出,只需要在将instance初始化为null,当需要获取实例(getInstance)的时候,判断instance是否为null,为null就创建对象,否则直接返回.

这样我们就实现了懒汉模式的单例吗?当然以上的程序在单线程的情况下是正确的,但是多线程的情况下会出现一些问题.

2.多线程下的懒汉模式

模拟多线程的情况下通过以上程序获取实例对象:

public class Demo02_SingletonLazy {
    public static void main(String[] args) {
        //创建是个线程
        for (int i = 0; i < 10; ++i) {
            Thread thread = new Thread(() -> {
                //获取实例对象并打印
                SingletonLazy instance = SingletonLazy.getInstance();
                System.out.println(instance);
            });
            thread.start();

        }
    }
}

打印的结果:

Java之单例模式

由打印的结果可以看出,发现获取到的对象不一定都是同一个对象,不符合我们的预期,发生了线程不安全的现象

3.分析线程不安全的原因

刚开始的时候instance=null;

Java之单例模式

 可以观察出来,仅有两个线程的时候,就有可能有两个不同的对象.

其实我们仔细思考可以想出具有不同的对象只会发生没有对象的初期,后期对象不为空的时候,多个线程再进来,对象也不会发生改变了,但我们要求的是单例,所以我们要完全满足单例模式!

为了解决这个问题,我们可以给这个加锁

4.加锁解决这个问题

我们在if循环外面加上锁

public class SingletonLazy {
    private static SingletonLazy instance = null;

    private SingletonLazy() {

    }

    public static SingletonLazy getInstance() {
        synchronized (SingletonLazy.class) {
            //在获取单例对象的时候,判断是否已经被创建,没有创建则创建
            if (instance == null) {
                instance = new SingletonLazy();
            }
        }
        
        return instance;
    }
}

打印结果:

Java之单例模式

 我们考虑以下,是否能将synchronized包裹的代码块发到if语句的下面

public class SingletonLazy {
    private static SingletonLazy instance = null;

    private SingletonLazy() {

    }

    public static SingletonLazy getInstance() {
        //在获取单例对象的时候,判断是否已经被创建,没有创建则创建
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                instance = new SingletonLazy();
            }
        }

        return instance;
    }
}

打印的结果:

Java之单例模式

是不可以的!Load和CMP操作没有原子执行,线程1加载到instace=null,线程2加载到的instace=null,所以线程1和线程2都是new出来一个新的SingletonLazy对象,所以要扩大加锁的范围

进行修改之后看起来就是正确的了,但是不高效.

5.双重检查锁

前面我们已经分析了,线程不安全的线程只存在于前期,也就是instace=null进入到方法的时期,instance!=null的时候是不会发生线程不安全的现象的,但是我们之前写的代码,不论在什么时期都要参与锁竞争,而锁竞争是十分耗费系统资源.

用户态:Java层面,JVM中执行的代码

内核态:执行的是CPU指令,加入synchronized后参与锁竞争就从用户态到内核态,而内核态执行效率没有用户态执行效率高.

来举一个现实中的例子:拿批改作业为例,同学们可以自己批改作业(用户态),也可以上交给老师进行批改(内核态),自己批改效率是很高的,但是交给老师,老师可能要备课,也要批改别人的作业,所以完成你作业的批改是十分不高效的.

public class SingletonLazy {
    private static SingletonLazy instance = null;

    private SingletonLazy() {

    }

    public static SingletonLazy getInstance() {
        if (instance == null) {
            //在获取单例对象的时候,判断是否已经被创建,没有创建则创建
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }

        return instance;
    }
}

这种用双重if判断的方式叫做双重检查锁,两层if语句判断的目的不一样,外层if是判断是否为空,避免参与锁竞争,内层if判断是否为空,为instance进行赋值.

初始状态instance=null,外层if判断为true,进入,此时三个线程参与锁竞争,线程1拿到了锁,线程2和线程3处在堵塞状态,线程1内层if判断为true,执行instance的赋值操作,之后释放锁资源,返回赋值之后的对象,线程2拿到了锁资源,内层if判断为false,释放锁资源,返回instance,线程3拿到锁资源后,和线程2一样.

之后instance!=null,外层if判断为false,直接返回instance,不要参与锁竞争,提高了效率.

Java之单例模式Java之单例模式

现在已经做到了确保原子性,内存可见性,但是还没有确保有序性,下面我们做进一步的优化

6.加volatile优化(单例懒汉模式最终版)

给共享变量instance加volatile关键字,可以避免指令重排序问题.

public class SingletonLazy {
    private static volatile SingletonLazy instance = null;

    private SingletonLazy() {

    }

    public static SingletonLazy getInstance() {
        if (instance == null) {
            //在获取单例对象的时候,判断是否已经被创建,没有创建则创建
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }

        return instance;
    }
}

懒汉模式的优点:节约资源.文章来源地址https://www.toymoban.com/news/detail-442928.html

到了这里,关于Java之单例模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式

    🍎 个人博客: 个人主页 🏆 个人专栏: JAVA ⛳️   功不唐捐,玉汝于成 目录 前言 正文 懒汉式(Lazy Initialization): 双重检查锁定(Double-Checked Locking): 结语 我的其他博客 在软件设计中,单例设计模式是一种重要的设计思想,它确保了一个类只有一个实例,并提供了一

    2024年01月15日
    浏览(55)
  • 【Java 设计模式】创建型之单例模式

    在软件开发中,单例模式是一种常见的设计模式, 它确保一个类只有一个实例,并提供一个全局访问点 。单例模式在需要控制某些资源,如数据库连接池、线程池等共享资源的情况下非常有用。在本文中,我们将介绍 Java 设计模式中的单例模式,了解其实现方式、使用场景

    2024年01月18日
    浏览(55)
  • Java23种设计模式-创建型模式之单例模式

    单例模式 (Singleton Pattern):通过单例模式的方法创建的 类在当前进程中只有一个实例 (根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例),该类负责 创建自己的对象 ,同时 确保只有单个对象 被创建。 注 : 1、单例类 只能 有 一个实例 。

    2024年04月26日
    浏览(54)
  • 手写java设计模式之单例模式,附源码解读

    在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处: 1、减少类的频繁创建,减少使用频繁使用new创建实例,减少GC压力。 2、某些应用场景下,使用单例模式,保证整个系统中只会创建一个类。 单例模式分两种:饿汉模式和懒汉模

    2024年04月29日
    浏览(48)
  • Java中static的应用之单例模式

    单例模式是一种创建对象的设计模式,它保证一个类只有一个实例,并提供一个全局访问点。由于单例模式只允许存在一个实例,因此它可以节省系统资源并提高程序的性能。在许多情况下,单例模式在应用程序中都是非常有用的,例如数据库连接、日志记录、配置文件等。

    2024年02月12日
    浏览(46)
  • Java课堂|独一无二的事物(设计模式之单例模式)

    本文主要讲述 单例模式 ,文中使用通俗易懂的案例,使你更好的学习本章知识点并理解原理,做到有道无术。 单例模式是23种设计模式中 创建型模式 的一种,通过单例模式的方法创建的类在当前进程或者线程中只有一个实例。单例模式有两种比较常见的实现方式: 饿汉式

    2024年02月07日
    浏览(44)
  • Java设计模式之单例模式详解--独一无二的事物

    本文主要讲述 单例模式 ,文中使用通俗易懂的案例,使你更好的学习本章知识点并理解原理,做到有道无术。 单例模式是23种设计模式中 创建型模式 的一种,通过单例模式的方法创建的类在当前进程或者线程中只有一个实例。单例模式有两种比较常见的实现方式: 饿汉式

    2024年02月07日
    浏览(45)
  • Java设计模式之单例模式详解(懒汉式和饿汉式)

    在开发工作中,有些类只需要存在一个实例,这时就可以使用单例模式。Java中的单例模式是一种常见的设计模式,它确保一个类只有一个实例,并提供全局访问点。下面来介绍一下两种常见的单例模式:懒汉式和饿汉式。 懒汉式属于一种延迟加载的单例模式,它的特点是在

    2024年02月15日
    浏览(45)
  • Java面试之单例模式的六种实现方式

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 由于设计模式在面向对象中起着举足轻重的作用,在面试中很多公司都喜欢问一下有关设计模式的问题。在常用的设计模式中,Singleton单例模式是唯一一个能用短短几十行代码完整实现的模式,因此,写

    2024年02月10日
    浏览(44)
  • 【前端设计模式】之单例模式

    在前端开发中,单例模式是一种常见的设计模式,用于确保一个类只有一个实例,并提供全局访问点。在实现单例模式时,有一些最佳实践和高级技巧可以帮助我们编写更优雅和可维护的代码。 使用闭包是实现单例模式的一种常见方法。通过将类的实例保存在闭包中,并提供

    2024年02月09日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包