单例加锁和不加锁的区别

2024-03-26 22:48
文章标签 单例 区别 加锁

本文主要是介绍单例加锁和不加锁的区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一,单线程模式单例

// 单线程单例
+(instancetype)sharedLoadData
{static Singleton *singleton;if (!singleton ) {singleton = [[Singleton alloc] init];}return singleton;
}

1.单线程单例只有在单个线程使用的情况下使用,在多线程的情况下,会产生线程不安全的情况,严格意义上来说,我们还需要把alloc方法变为私有方法才行,严格的单例是不允许再创建其他实例的,而alloc方法可以在外部任意生产实例。换句话说,假如在两条线程里调用sharedLoadData方法,可能会产生两个singleton实例,这样单例就失去意义了。

二,多线程加锁单例

// @synchronized加锁
+(instancetype)sharedLoadData
{static Singleton *singleton;@synchronized (self) {if (!singleton) {singleton = [[Singleton alloc] init];}}return singleton;
}

2.加锁以后,当多个线程同时调用sharelnstanc时,由于@synchronized已经加锁,只能有一个线程创建singleton实例。这样就解决了第一种情况的弊端,但是也有缺点:只有在singletion未创建时,加锁才是必要的,如果singleton已经创建,这个时候还加锁的话,会影响性能。

三,系统GCD创建单例

+(instancetype)sharedLoadData
{static Singleton *singleton = nil;static dispatch_once_t onceToken;// dispatch_once  无论使用多线程还是单线程,都只执行一次dispatch_once(&onceToken, ^{singleton = [[Singleton alloc] init];});return singleton;
}

3.GCD创建单例不仅可以解决多条线程的线程安全问题,也能保证性能。

dispatch_one主要是根据onceToken的值来决定怎么去执行代码,

1.当onceToken = 0时,线程执行dispath_once的block中代码

2.当onceToken = -1时,线程跳过dispatch_one的block中的代码不执行,

3.当onceToken为其他值时,线程被阻塞,等待onceToken值改变。

当线程调用sharelnstance,此时onceToken =0 ,调用block中的代码,此时onceToken的值变为140734537148864。当其他线程再调用shareinstance方法时,onceToken的值已经是140734537148864,线程阻塞。当block线程执行完block之后,onceToken变为-1,其他线程不再阻塞,跳过block。下次再调用sharelnstance时,block已经为-1,直接跳过blokc. 

四,单例实现方式与优缺点

1、懒汉模式:实现原理和懒加载其实很像,如果在程序中不使用这个对象,那么就不会创建,只有在你使用代码创建这个对象,才会创建。这种实现思想或者说是原理都是iOS开发中非常重要的,所以,懒汉式的单例模式也是最为重要的,是开发中最常见的。


2、饿汉模式:在没有使用代码去创建对象之前,这个对象已经加载好了,并且分配了内存空间,当你去使用代码创建的时候,实际上只是将这个原本创建好的对象拿出来而已。


3.使用GCD代替手动锁实现单例模式


4.使用宏封装直接便于开发使用

(1).懒汉模式

static id instance = nil;// 懒加载 线程不安全 单例
+ (instancetype) ShareInstance
{if (instance == nil) {instance = [[self alloc] init];}return instance;
}// 懒加载  加锁  单例
+ (instancetype) ShareInstance1
{@synchronized (self) { //为了线程安全,加上互斥锁if (instance == nil) {instance = [[self alloc] init];}}return instance;
}

1)加synchronized 是为了保证单例的读取线程安全,为什么需要添加synchronized

+(instancetype)sharedSingleton{
static id instance = nil;if (!instance) {@synchronized (self) {instance = [[self alloc] init];}}return instance;
}

肯定不行的,稍微对synchronized 有点了解就知道这种只是“锁”住了对象的创建,没有“锁”住 if 判断。如果两个线程都进到了 if 里面,一样可以生成两个对象。

2)static
修饰局部变量:

修饰了局部变量的话,那么这个局部变量的生命周期就和不加static的全局变量一样了(也就是只有一块内存区域,无论这个方法执行多少次,都不会进行内存的分配),不同的在于作用域仍然没有改变

修饰全局变量:

如果不适用static的全局变量,我们可以在其他的类中使用extern关键字直接获取到这个对象,可想而知,在我们所做的单例模式中,如果在其他类中利用extern拿到了这个对象,进行一个对象销毁,

extern id instance;
instance = nil;

这时候在这句代码之前创建的单例就销毁了,再次创建的对象就不是同一个了,这样就无法保证单例的存在,所以对于全局变量的定义,需要加上static修饰符

3) allocWithZone 与copyWithZone方法 

我们在项目中一般是直接调用自己定义的类方法:ShareInstance,但是有时候也会调用alloc方法直接对单例进行初始化,那么也会导致没有产生该单例,所以我们需要保证应用中只有一个该类的对象需要重写它的allocWithZone 方法,alloc调用的底层也是allocWithZone方法,直接与上述的单例方法类同。

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{// 解决多线程问题@synchronized(self){if (instance == nil) {// 调用super的allocWithZone方法来分配内存空间instance = [super allocWithZone:zone];}}return instance;
}

如果使用copy创建出新的对象的话,那么就不能够保证单例的存在了也会导致同样的问题。此处直接返回instance就可以了。

- (id)copyWithZone:(NSZone *)zone
{return instance;
}

(2).饿汉模式 

在没有使用代码去创建对象之前,这个对象已经加载好了,并且分配了内存空间,当你去使用代码创建的时候,实际上只是将这个原本创建好的对象拿出来而已。在alloc之前如何将对象直接赋值呢,有两种方式:load和initialize。

load 会在类加载到运行环境中的时候就会调用且仅调用一次,同时注意一个类只会加载一次(类加载有别于引用类,可以这么说,所有类都会在程序启动的时候加载一次,不管有没有在目前显示的视图类中引用到)initialize方法:当第一次使用类的时候加载且仅加载一次

static id instance = nil;+ (void)load
{instance = [[self alloc]init];
}+ (void)initialize
{instance = [[self alloc]init];
}+ (instancetype)allocWithZone:(struct _NSZone *)zone
{if (instance == nil) {instance = [super allocWithZone:zone];}return instance;
}
+ (instancetype)sharedInstance
{return instance;
}
- (id)copyWithZone:(NSZone *)zone
{return instance;
}

实际上只需要实现 load 与 initialize 其中一种即可实现单例。 

 

 

这篇关于单例加锁和不加锁的区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/850108

相关文章

hevc和H.264格式的区别

HEVC(High Efficiency Video Coding)和H.264(也称为Advanced Video Coding,AVC)都是视频压缩标准,但它们之间存在一些显著的区别,主要集中在压缩效率、资源需求和兼容性方面。 压缩效率 HEVC,也被称为H.265,提供了比H.264更高的压缩效率。这意味着在相同的视频质量下,HEVC能够以大约一半的比特率进行编码,从而减少存储空间需求和

Java面试题:通过实例说明内连接、左外连接和右外连接的区别

在 SQL 中,连接(JOIN)用于在多个表之间组合行。最常用的连接类型是内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。它们的主要区别在于它们如何处理表之间的匹配和不匹配行。下面是每种连接的详细说明和示例。 表示例 假设有两个表:Customers 和 Orders。 Customers CustomerIDCus

Eclipse+ADT与Android Studio开发的区别

下文的EA指Eclipse+ADT,AS就是指Android Studio。 就编写界面布局来说AS可以边开发边预览(所见即所得,以及多个屏幕预览),这个优势比较大。AS运行时占的内存比EA的要小。AS创建项目时要创建gradle项目框架,so,创建项目时AS比较慢。android studio基于gradle构建项目,你无法同时集中管理和维护多个项目的源码,而eclipse ADT可以同时打开

在 Java 中,JDK、JRE、JVM 分别代表什么,有何关系和区别?

在Java开发的世界中,我们会经常听到JDK、JRE和JVM这三个词。它们都与Java的运行环境以及Java程序的编译和运行有关,它们之间也存在一些关联性和区别。 什么是JDK、JRE和JVM 我们来看它们分别是什么。 JDK,全称Java Development Kit,即Java开发工具包。顾名思义,JDK是用于Java开发的一套工具包,里面包含了Java的编译器javac、

QT 中ListView和ListWidget有什么区别

ListView和ListWidget在Qt框架中都是用于显示列表数据的控件,但它们在使用方法和特性上存在一些明显的差异。以下是关于它们用法不一样的地方的详细分析: 数据管理方式: ListView:使用QAbstractItemModel数据模型来管理和显示列表数据。QAbstractItemModel是一个抽象类,允许开发者自定义数据模型以适应特定的数据结构和需求。这使得ListView在处

Transformers和Langchain中几个组件的区别

1.对于Transformers框架的介绍 1.1 介绍: transformers 是由 Hugging Face 开发的一个开源库,它提供了大量预训练模型,主要用于自然语言处理(NLP)任务。这个库提供的模型可以用于文本分类、信息抽取、问答、文本生成等多种任务。 1.2 应用场景: 文本分类:使用 BERT、RoBERTa 等模型进行情感分析、意图识别等。命名实体识别(NER):使用序列

java的clone() 深克隆与浅克隆的区别

克隆的目的:快速创建一个已有对象的副本。 克隆的步骤: 创建一个对象将原有对象的数据导入到新创建的数据中 1. Object的clone()源代码简介 [java]  view plain copy /**   * Creates and returns a copy of this {@code Object}. The default   * i

HTTP状态码中301与302的区别

一.官方说法  301,302 都是HTTP状态的编码,都代表着某个URL发生了转移,不同之处在于:  301 redirect: 301 代表永久性转移(Permanently Moved)。  302 redirect: 302 代表暂时性转移(Temporarily Moved )。  这是很官方的说法,那么它们的区别到底是什么呢?  1.1、什么是301转向?什么是301重定向?

什么是dB?dBm、dBc、dBi、dBd怎么计算,有什么区别?

什么是dB?dBm、dBc、dBi、dBd怎么计算,有什么区别? 引言 在电子工程、通信和音频领域,dB(分贝)是一个常见的术语。许多人刚接触时可能会感到困惑,因为它不仅仅是一个简单的单位,还有多种不同的形式,如dBm、dBc、dBi和dBd。这篇文章将详细解释这些概念,并介绍如何计算它们,帮助初学者更好地理解和应用。 什么是dB? dB,即分贝,是一种表示两个数值比值的对数单位。分贝的基

工程师 - status和state的区别

"Status"和 "state"是相关的概念,但有不同的含义,尤其是在计算、系统和编程方面: 1. Status:     * 定义: 状态是指系统、进程或实体在某一特定时间点的当前状态或情况。     * 使用方法: 它通常描述一项操作的状态,如是否正在进行、是否已成功完成、是否遇到错误或是否正在等待输入。     * 举例说明: 在编程中,函数可能会返回一个状态代码,