Java多线程基础-8:单例模式及其线程安全问题(一)

简介: 本文介绍了设计模式中的单例模式,它是软件开发中的经典模式,确保某个类在程序运行期间只有一个实例。

例模式是经典的设计模式之一。什么是设计模式?代码的设计模式类似于棋谱,棋谱就是一些下棋的固定套路,是前人总结出来的一些固定的打法。依照棋谱来下棋,不说能下得非常好,但至少是有迹可循,不会下得很糟糕。代码的设计模式也是一样。


设计模式,就是软件开发中的棋谱。一些编程界的大佬,针对一些常见情景总结出了一些代码的“编写套路”。按照这样的套路来写代码,不说能写得非常好,但也至少不会写得太糟糕。以前有一个大佬写了一本书,名叫《讨论二十三种设计模式》,这本书广为流传,这里的设计模式也就是我们上面说到的。


事实上设计模式远不止“二十三种”。以下两种设计模式经常遇到:


单例模式

工厂模式

本文主要介绍单例模式。



一、什么是单例模式?


单例指的就是单个实例(instance),也就是单个对象(对象就是类的实例)。单例模式指的是某个类在进程中只有唯一一个实例(在一个程序中,只能创建一个实例(一个对象),不能创建多个对象)。


按理来说,在写代码的时候多 new 几次,就能创建多个对象了。但在语法上,是有办法禁止这样多 new 几次的操作的。


也就是说,Java中的单例模式,实际上是借助 Java 语法,保证某个类只能够创建出一个实例,而不能被new多次。


为什么会有这样的用途?其实原因是很简单的:在有些场景下,本身它就要求某个概念是单例的。比如每个人只能同时拥有一个配偶。



二、如何实现单例模式?


Java实现单例模式的方式有很多种,这里我们主要介绍两种写法:


饿汉模式(急迫)

懒汉模式(从容)

如何理解 饿汉模式 和 懒汉模式 呢?饿汉模式就好比每次吃完饭之后,立刻就把碗给洗了(主打的就是一个急迫);懒汉模式则是每次吃完饭了,先把碗放到一边先不洗,等到吃下一顿了再洗。通常认为,懒汉模式更好,效率更高(非必要不洗碗)。


比如,中午吃饭用了4个碗,那么饿汉模式就得一次性把4个碗都洗了;而晚上吃饭要用2个碗,懒汉模式就只需要洗4个碗当中用不到的2个碗就行了。洗2个碗明显要比洗4个碗效率更高(不考虑没洗的碗会变臭~~只考虑效率)。


在计算机中的例子:打开一个硬盘上的文件,读取文件内容并显示出来。


饿汉:把文件所有内容都读到内存中,并显示。

懒汉:只把文件读一小部分,把当前屏幕填充上。如果用户翻页了,再读其它文件内容;如果不翻页,就省下了。

在这样的情况下,懒汉模式也是完胜饿汉模式的。


假设要读取的文件非常大,有 10G,按照饿汉模式的方式,文件打开可能都要卡半天,更何况还有内存是否足够的问题。


但懒汉模式下就可以很快速地打开,因为它只读取 1 页的内容,1 页也就几百字,可能也就读取 2k 就够了;如果用户还要读其它页的内容,懒汉再从内存里读取相应的内容,用户浏览不到的页面,也就不将它的内容加载到内存中了。


(虽然懒汉模式会增加硬盘的读取次数,但和饿汉模式的情况相比,是不值一提的。)


下面我们来看看如何用代码实现这两种单例模式。


1、代码实现:饿汉模式


a.饿汉模式的构造思路

我们先初步地创建出 Singleton 类,并在里面把对象创建出来:

// 把一个类设置成单例的
class Singleton {
    // 唯一实例的本体
    private static Singleton instance = new Singleton();    // 把对象创建出来
 
    // 获取到实例的方法
    public static Singleton getInstance() {
        return instance;
    }
}


注意:这里的 instance 属性要用 static 修饰,static变量保存了单例对象的唯一实例。



同时,将 instance 属性用private封装,并提供一个get方法。这样,我们就可以从外部获取instance了:



public class Test {
    public static void main(String[] args) {
        // 此时 s1 和 s2 是同一个对象
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
 
    }
}


显然,此处的 s1 和 s2 获取到的实际是同一个对象。


但是,上述的代码并没有限定再次 new 对象的操作:



此处的 s3 也显然与 s1 和 s2 不是同一个对象。因此,此处必须把 new 操作给禁止掉。采用的方式是 构造方法私有化。


将构造方法用 private 修饰,可以发现,此时我们上面的 new 操作就报错了,无法通过编译。


有些同学可能会想到,用反射仍然可以获取到私有方法。一方面,反射本身就是一种非常规的手段,它本身就是不安全的;另一方面,单例模式有一种实现方式,借助枚举,也可以保证反射下的安全,这个在此不过多介绍。


b.总结:饿汉模式代码


class Singleton {
    private static Singleton instance = new Singleton();
 
    public static Singleton getInstance() {
        return instance;
    }
 
    private Singleton(){ }
}
public class Test {
    public static void main(String[] args) {
        //此时s1和s2是同一个对象
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
    }
}


将一个类设置成单例的


通过Java语法来限制类实例的多次创建,从而实现单例模式:


static是用于类级别的数据共享,保存了单例对象的唯一实例,可以在单例模式中用来单例实例的唯一性。

单例模式中将构造方法私有化,可以避免外部直接创建新的实例。

但饿汉模式的有一个问题,那就是实例的创建时机过早了。只要类一加载,就会创建出这个实例,可要是后面并没有用到这个实例呢?


更好的实现方式是懒汉模式。


2、代码实现:懒汉模式


懒汉模式的核心思想:非必要,不创建。懒汉模式和饿汉模式的代码实现类似,最大的区别是饿汉模式在不使用instance对象时,不把它new出来。


以下代码就是懒汉模式的实现:


class SingletonLazy {
    //先令instance引用为null
    private static SingletonLazy instance = null;
 
    //获取instance实例
    public static SingletonLazy getInstance() {
        if(instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
 
    //构造方法私有化
    private SingletonLazy() { }
}





Java多线程基础-8:单例模式及其线程安全问题(二)+

https://developer.aliyun.com/article/1520525?spm=a2c6h.13148508.setting.14.75194f0eujcMau

相关文章
|
5月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
202 0
|
2月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
160 1
|
2月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
177 1
|
5月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
6月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
381 5
|
6月前
|
监控 搜索推荐 Java
Java 多线程最新实操技术与应用场景全解析:从基础到进阶
本文深入探讨了Java多线程的现代并发编程技术,涵盖Java 8+新特性,如CompletableFuture异步处理、Stream并行流操作,以及Reactive编程中的Reactor框架。通过具体代码示例,讲解了异步任务组合、并行流优化及响应式编程的核心概念(Flux与Mono)。同时对比了同步、CompletableFuture和Reactor三种实现方式的性能,并总结了最佳实践,帮助开发者构建高效、扩展性强的应用。资源地址:[点击下载](https://pan.quark.cn/s/14fcf913bae6)。
381 3
|
7月前
|
算法 Java 调度
Java多线程基础
本文主要讲解多线程相关知识,分为两部分。第一部分涵盖多线程概念(并发与并行、进程与线程)、Java程序运行原理(JVM启动多线程特性)、实现多线程的两种方式(继承Thread类与实现Runnable接口)及其区别。第二部分涉及线程同步(同步锁的应用场景与代码示例)及线程间通信(wait()与notify()方法的使用)。通过多个Demo代码实例,深入浅出地解析多线程的核心知识点,帮助读者掌握其实现与应用技巧。
134 1
|
7月前
|
Java
java 多线程异常处理
本文介绍了Java中ThreadGroup的异常处理机制,重点讲解UncaughtExceptionHandler的使用。通过示例代码展示了当线程的run()方法抛出未捕获异常时,JVM如何依次查找并调用线程的异常处理器、线程组的uncaughtException方法或默认异常处理器。文章还提供了具体代码和输出结果,帮助理解不同处理器的优先级与执行逻辑。
167 1
|
2月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
149 6

热门文章

最新文章