文章目录
- 前言
- 一.什么是单例模式
- 二.在java中的单例模式
-
* 2.1 饿汉式的介绍
- 2.2 懒汉式的介绍
- 三 懒汉式的单例模式,线程不安全的解决方式
-
* 3.1 造成线程不安全的原因
- 3.2 解决方案
- 3.3 总结
前言
这篇文章,我们会介绍一下单例模式,但这里的单例模式,不是我们所说的设计模式,当然听到设计模式,大家一定都说,我当然知道设计模式了,有23种呢?一下子一顿输出,当然我这里说的单例模式还是跟设计模式有一些区别的,当然我不做概述,因为我也没咋个去了解过设计模式,我把大家拉回来,什么是多线程对的单例模式呢?看完我以下的解释相信你会明白的.
一.什么是单例模式
多线程环境下的单例模式需要保证只有一个实例对象被创建,并且可以在多线程环境下安全地访问该实例。
概念性的东西就是这样,把这段话,反复读,相信你也读不出来什么结果.简单来说的话,就是我现在只能娶一个老婆,不能娶多个老婆一样的意思,如果我娶多个老婆的话,在现在看来就是犯法的,当然了,小伙伴们,可不敢这样想,我想要后宫佳丽3000,这种想想就行了,哈哈
再来说说,在java中的单例模式的情况,Java 中的单例模式,借助java语法,保证某个类,只能够创建出一个实例,而不能new
多次.下面我们继续来看看在java中如何实现单例模式.
二.在java中的单例模式
怎么说呢?在Java中实现单例模式主要有以下几种:
- 饿汉式
- 懒汉式
- 双重检查锁
- 静态内部类
- 枚举
当然,我们或许没列举完,如果有其他的,也请各位小友,补充以下,当然我也不会全部介绍完,只会详细的介绍饿汉式和懒汉式.
2.1 饿汉式的介绍
饿汉式,大家通常想到饿这个词的,通常就会跟狼吞虎咽这个词语联系到一起,当然,我举个简单的例子,就拿取快递来说吧,你买了8件快递,今天到了2件,你马上就会去取快递.这就是饿汉式,遇事就马上下决定,真男人,绝不退缩,突出一个猛.
了解完什么是饿汉式,我们来看一看,饿汉式的代码实现.
//把这个类设置成单例的
public class Singleton {
private static Singleton instant = new Singleton();
//获取实例的方法
public static Singleton getInstance() {
return instant;
}
//禁止外部new实例
private Singleton() {
}
;
}
class ThreadDemo16{
public static void main(String[] args) {
//此时的s1
// 此时 s1 和 s2 是同一个对象!!
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 ==s2);
}
}
简单来说,我们是怎么去实现这个饿汉式的单例模式的呢,主要进行了俩点,你注意看
我们这一句:
private static Singleton instant = new Singleton();
是类内部把实例创建好.
另外一个步骤就是,我们在创建对象,进行实例化的时候,我们吧构造方法私有化.
private Singleton() {
}
这俩个步骤,我们自然就实现了单例模式,保证只有一个实例对象被创建.
当然我们既然谈到线程,我们自然就要讨论一个问题,你觉的.我们饿汉式的方式是线程安全的吗?如果你看过,我写的那个线程安全的类别时候,我们就知道,是否会发生线程安全的问题了.
总的来说,这上面几种情况都是没有出现的,因此我们初步判断饿汉式是线程安全的,但是也不完全是,有些资料说会在类加载的时候就创建实例,如果该实例很大或者初始化过程很耗时,会造成资源浪费。这点值不值得考究,我就不知道了,但从表面上来说,我们并没有从饿汉式中看出什么端倪.
2.2 懒汉式的介绍
懒汉式,我们自然就注重一个懒字,那我们为啥要叫懒汉式呢?其实吧,我再拿快递的例子去解释一下,假设你买了八个东西,但突然有一个快递到了,但是你不会立刻去取,你要等到它们都到了,一起去取,这就是懒汉式,理解了这个概念之后,我们就看一下java代码的概念.
import com.sun.org.apache.regexp.internal.RE;
class SingleLazy{
private static SingleLazy instance=null;
private SingleLazy(){}
public static SingleLazy getInstance(){
if (instance == null){
instance=new SingleLazy();
}
return instance;
}
}
public class ThreadDemo18 {
public static void main(String[] args) {
SingleLazy s1 = SingleLazy.getInstance();
SingleLazy s2 = SingleLazy.getInstance();
System.out.println(s1 == s2);
}
}
看到了懒汉式的代码.你就可以看出来,跟饿汉式的区别是,懒汉式,就是非必要时候,不创建对象.具体是哪一句呢?我这里给你举出来,你可以思考一下,我说的是不是对的.
public static SingleLazy getInstance(){
if (instance == null){
instance=new SingleLazy();
}
return instance;
这里加了判断语句,非必要的时候,不创建对象.
当然我们居然说了这种单例模式后,你来思考一下,它是不是线程安全的呢?其实简单来说,它是不是线程安全的,判断还是很简单的,因为你这样看,我们这里有判断条件,有判断条件,就涉及到赋值判断操作,你想象多线程环境下,线程1会对起进行判断,线程2也会进行判断,万一判断不是同时进行的,那不就整了个乌龙时间了吗?另外我们再对照着线程不安全的情况,仔细的思考一下.
所以说,懒汉式会破坏单例模式的原则,导致线程不安全,但是有没有方式解决呢?答案是有的,接下来我就会解释,我们怎么去避免这个问题,所以大家还是不要心急,细细听我道来.
三 懒汉式的单例模式,线程不安全的解决方式
3.1 造成线程不安全的原因
大家不要嫌我啰嗦,我再说明一下原因,这样我们才好说出解决策略
当多个线程同时访问getInstance()方法时,可能会出现竞态条件,即两个线程同时执行了if (instance ==
null)语句,导致创建了两个实例。这种情况下,线程单例模式就会失效。这个东西,就是我们说的会导致的线程安全问题.
接下来我们来提供一个解决方案.
3.2 解决方案
当然哈,我们开始说了是线程安全的原因,就是在创建对象的那一步,出现了,多线程竞争的关系,那么我们是不是可以直接在创建对象的那一步,加锁.代码如下:
public static SingleLazy getInstance(){
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
return instance;
}
当然,我们这一步的操作,其实是保证了,创建对象一开始的原子性.
当然,加锁的操作,我们还是谨慎的,由于加锁的机制,当多个线程同时调用该方法时,只能有一个线程进入临界区创建实例,其他线程则被阻塞,可能会导致性能瓶颈。因此我们要避免就是非必要情况不加锁,就拿当前的这个例子来说,我们首次进入这个代码段的时候,如果对象已经创建了,我们就不进行加锁,对象没有创建,我们就加锁,因此外面就必须再嵌套一层循环.
public static SingletonLazy getInstance() {
// 这个条件, 判定是否要加锁. 如果对象已经有了, 就不必加锁了, 此时本身就是线程安全的.
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
当然大家不要被这个这俩个if条件所迷惑,一个避免过度加锁,第二个if判断就是避免多线程对其进行同时操作.
到这里,大家觉得我们线程安全的问题结束了吗?其实并没有,因为我们创建对象的时候,还会发生指令重排序.因此我们还必须进行下一步的优化,代码如下所示:
lass SingletonLazy {
volatile private static SingletonLazy instance = null;
public static SingletonLazy getInstance() {
// 这个条件, 判定是否要加锁. 如果对象已经有了, 就不必加锁了, 此时本身就是线程安全的.
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
我们来解释一下为什么会出现指令重排序问题,如果你看过我的上一篇线程安全的粗略详解,你就大概明白了,这个问题,当然我们,再来说一下,我们这里为啥会出现现在的问题.
因为创建实例的代码通常会被拆分成多个指令,包括分配内存空间、初始化对象、将对象赋值给变量等。如果编译器或处理器为了提高性能而进行指令重排序,可能会导致上述指令的执行顺序与代码的顺序不一致。这样,如果一个线程在另一个线程完成了对象赋值操作之前执行了if语句的第一个条件判断,就会错误地认为实例已经被创建,从而导致程序出错。
为了解决这个问题,可以使用volatile关键字来修饰单例实例变量,这样可以保证线程对该变量的读写操作都是原子的,并且禁止指令重排序
3.3 总结
最后让我们小小的总结一下,我们解决的办法
1.加锁,把if 和new变成原子操作.
⒉.双重 if,减少不必要的加锁操作
3.使用volatile禁止指令重排序,保证后续线程肯定拿到的是完整对象
接下来我将给各位同学划分一张学习计划表!
学习计划
那么问题又来了,作为萌新小白,我应该先学什么,再学什么?
既然你都问的这么直白了,我就告诉你,零基础应该从什么开始学起:
阶段一:初级网络安全工程师
接下来我将给大家安排一个为期1个月的网络安全初级计划,当你学完后,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web渗透、安全服务、安全分析等岗位;其中,如果你等保模块学的好,还可以从事等保工程师。
综合薪资区间6k~15k
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)
2、渗透测试基础(1周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等
3、操作系统基础(1周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)
4、计算机网络基础(1周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现
5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固
6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)
那么,到此为止,已经耗时1个月左右。你已经成功成为了一名“脚本小子”。那么你还想接着往下探索吗?
阶段二:中级or高级网络安全工程师(看自己能力)
综合薪资区间15k~30k
7、脚本编程学习(4周)
在网络安全领域。是否具备编程能力是“脚本小子”和真正网络安全工程师的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力。
零基础入门的同学,我建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习
搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP,IDE强烈推荐Sublime;
Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,没必要看完
用Python编写漏洞的exp,然后写一个简单的网络爬虫
PHP基本语法学习并书写一个简单的博客系统
熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选)
了解Bootstrap的布局或者CSS。
阶段三:顶级网络安全工程师
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!
学习资料分享
当然,只给予计划不给予学习资料的行为无异于耍流氓,这里给大家整理了一份【282G】的网络安全工程师从入门到精通的学习资料包,可点击下方二维码链接领取哦。
![](https://img-blog.csdnimg.cn/img_convert/92afb888a23fa720466b195c3cd2b674.jpeg)