单例加锁和不加锁的区别

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

相关文章

Conda与Python venv虚拟环境的区别与使用方法详解

《Conda与Pythonvenv虚拟环境的区别与使用方法详解》随着Python社区的成长,虚拟环境的概念和技术也在不断发展,:本文主要介绍Conda与Pythonvenv虚拟环境的区别与使用... 目录前言一、Conda 与 python venv 的核心区别1. Conda 的特点2. Python v

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现

Before和BeforeClass的区别及说明

《Before和BeforeClass的区别及说明》:本文主要介绍Before和BeforeClass的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Before和BeforeClass的区别一个简单的例子当运行这个测试类时总结Before和Befor

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Linux中的more 和 less区别对比分析

《Linux中的more和less区别对比分析》在Linux/Unix系统中,more和less都是用于分页查看文本文件的命令,但less是more的增强版,功能更强大,:本文主要介绍Linu... 目录1. 基础功能对比2. 常用操作对比less 的操作3. 实际使用示例4. 为什么推荐 less?5.

Java 关键字transient与注解@Transient的区别用途解析

《Java关键字transient与注解@Transient的区别用途解析》在Java中,transient是一个关键字,用于声明一个字段不会被序列化,这篇文章给大家介绍了Java关键字transi... 在Java中,transient 是一个关键字,用于声明一个字段不会被序列化。当一个对象被序列化时,被

解读@ConfigurationProperties和@value的区别

《解读@ConfigurationProperties和@value的区别》:本文主要介绍@ConfigurationProperties和@value的区别及说明,具有很好的参考价值,希望对大家... 目录1. 功能对比2. 使用场景对比@ConfigurationProperties@Value3. 核

Spring Boot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)

《SpringBoot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)》:本文主要介绍SpringBoot拦截器Interceptor与过滤器Filter深度解析... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实