鸿蒙开发5.0【基于单例实现跨线程缓存】

单例的使用场景很多,如可以通过单例实现应用缓存,这样多个线程统一对一块内存进行读写数据,既保障了数据的唯一性,又提高了业务处理性能。又比如使用单例实现应用的全局配置管理,保障全局有且仅有一个配置管理对象。

单例模式简介

单例是设计模式使用最为普遍的模式之一。它是一种对象创建模式,用于产生一个对象的具体实例,它可以确保系统中(单进程),一个类只产生一个实例。它的优势在于:

  1. 对于频繁使用的对象,可以省略new操作花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
  2. 由于new操作的次数减少,因此对系统内存的使用频率也会降低,这样将减少GC压力。

严格来说单例模式与并行没有直接的关系,是因为它太常见了,在应用开发的过程中将不可避免的会在多线程环境中使用到它。

创建一个单例类

由于单例类需要在多个模块间使用,因此单例类的本质是一个Sendable共享对象,创建一个单例类型最常用的有饿汉式和懒汉式,除此之外还有双重检查模式(这个不在本文中介绍)

注意:单例类需要使用[“use shared”]指令来标记,"use shared"需写在import语句之后,其他语句之前。

饿汉式

饿汉式:此种方式在类加载时,静态实例instance就已经创建并初始化好了。(这种方式当前有BUG,待BUG修复后删除此说明

"use shared"

@Sendable

export class SingletonClassE {

static instance: SingletonClassE = new SingletonClassE();



private constructor() {

}



public doSomething() {

}

}



//使用方式

function doSomething() {

SingletonClassE.instance.doSomething()

}

此处没有将instance申明为private类型并提供getInstance静态方法,得益于ArkTS的语法校验,外界无法通过SingletonClass.instance = null/undefined/new SingletonClass()的方式给instance实例赋值。

根据使用习惯,也可以将instance申明为private类型,并提供静态方法getInstance()获取instance实例。

"use shared"

@Sendable

export class SingletonClassE {

private static instance : SingletonClassE = new SingletonClassE();



private constructor() {

}



public static getInstance() : SingletonClassE {

if (!SingletonClassE.instance) {

SingletonClassE.instance = new SingletonClassE();

}

return SingletonClassE.instance;

}



public doSomething() {

}

}



//使用方式

function doSomething() {

SingletonClassE.getInstance().doSomething()

}
  • 优点

    单例对象在创建时是线程安全的。

    获取单例对象时不需要加锁。

  • 缺点

    类加载时即创建对象,无法实现懒加载。

一般认为延迟加载可以节省内存资源。但是延迟加载是不是真正的好,要看实际的应用场景,而不一定所有的应用场景都需要延迟加载。

懒汉式

懒汉式:将对象的创建延迟到了获取对象的时候,但为了线程安全,不得不为获取对象的操作加锁,这就导致了低性能。由于ArkTS提供的是异步锁,因此使用单例对象时,案例中提供了两种懒汉式单例的获取方式:

  • 异步方式使用getInstanceAsync调用方需要使用async/await的方式,或者promise/then的方式。下方代码有说明。
  • 同步方式使用getInstanceSync调用方在单例调用之前,需要先执行initSingletonClass。执行时机可以在自定义组件的aboutToAppear()中,或者在Ability的onCreate()中初始化。后续案例有介绍。如果initSingletonClass中有较长时间的执行逻辑,不建议使用这一套方案,因为会有调用getInstanceSync时,实例还未初始化好的风险
import utils from '@arkts.utils';

import { ArkTSUtils } from '@kit.ArkTS';

"use shared"



@Sendable

export class LazySingletonClass {

private static instance: LazySingletonClass;



private constructor() {

}



public static async getInstanceAsync() : Promise<LazySingletonClass> {

let lock: utils.locks.AsyncLock = utils.locks.AsyncLock.request('SingletonClass');

return lock.lockAsync(() => {

if (!LazySingletonClass.instance) {

LazySingletonClass.instance = new LazySingletonClass()

}

return LazySingletonClass.instance;

}, ArkTSUtils.locks.AsyncLockMode.EXCLUSIVE)

}



public static async initSingletonClass() {

let lock: utils.locks.AsyncLock = utils.locks.AsyncLock.request('SingletonClass');

return lock.lockAsync(() => {

if (!LazySingletonClass.instance) {

LazySingletonClass.instance = new LazySingletonClass()

}

}, ArkTSUtils.locks.AsyncLockMode.EXCLUSIVE)

}



public static getInstanceSync() {

return LazySingletonClass.instance

}



public doSomething() {

}

}



function doSomething() {

LazySingletonClass.getInstance().then((instance : LazySingletonClass) => {

instance.doSomething();

})

}



async function doSomethingAsync() {

(await LazySingletonClass.getInstance()).doSomething();

}
  • 优点:

    对象的创建是线程安全的。

    支持延迟加载。

  • 缺点:

    获取对象的操作被加上了锁,影响了并发度。如果单例对象需要频繁使用,那这个缺点就是无法接受的。

    由于异步锁的原因,需要在async方法中调用。

通过单例实现跨线程缓存

这是一个简单的例子,模拟单例实现内存管理的业务场景,总计15个线程分别进行增加和查询的操作,数据存储在单例对象LazySingletonClass中。

锁的使用

单例只是作为缓存的管理对象,因此提供公用缓存和基础接口(get/set),因此为了保证数据临界区的安全,锁需要在业务中进行管理(increaseNumber/queryNumber)

此处需要注意的是在increaseNumber中需要对数据操作的部分进行加锁,以保证数据的正确性。否则increase5、increase7、increase9在进入线程后,由于线程运行的时序不受控,因此通过getNum方法获取的指是不可预期的,最终输出无法达到预期。

案例中queryNumber也加了锁,是为了实现读写的互斥操作,模拟数据修改过程中不允许读取的业务场景(避免读取到脏数据)。

调用方式

  • 异步调用:queryNumber线程中,使用的是异步调用(await LazySingletonClass.getInstanceAsync()).getNum()
  • 同步调用在aboutToAppear中调用LazySingletonClass.initSingletonClass();对单例进行初始化在increase线程中,使用的是同步调用LazySingletonClass.getInstanceSync().setNum(increaseNum);

开发者可以结合自己的编程习惯选择采用同步或者异步方式。

while循环是为了模拟函数耗时。

import { LazySingletonClass } from './LazySingletonClass';

import utils from '@arkts.utils';



@Component

export struct SingletonPage {



aboutToAppear(): void {

LazySingletonClass.initSingletonClass();

}

build() {

NavDestination() {

Column() {

Text('单例模式')

Button('单例测试').onClick(event => {

console.info("==== onclick")

for (let i =0; i < 15; i++) {

if (i == 5 || i == 7 || i == 9) {

let increaseTask = new taskpool.Task("increase" + i, increaseNumber, "increase" + i)

taskpool.execute(increaseTask)

} else {

let queryTask = new taskpool.Task("query" + i, queryNumber, "query" + i)

taskpool.execute(queryTask)

}

}

})

}

.justifyContent(FlexAlign.SpaceEvenly)

.height('100%')

.width('100%')

}.hideTitleBar(true)

}

}



@Concurrent

export function queryNumber(taskName : string) {

let lock: utils.locks.AsyncLock = utils.locks.AsyncLock.request('numberlock');

return lock.lockAsync(async () => {

let start : number;

start = Date.now();

while (Date.now() - start < 100) {

// 模拟等待0.1秒

}

console.info("==== " + taskName + ' number is ' + (await LazySingletonClass.getInstanceAsync()).getNum())

}, ArkTSUtils.locks.AsyncLockMode.EXCLUSIVE)

}



@Concurrent

export function increaseNumber(taskName : string) {

console.info(taskName)

let lock: utils.locks.AsyncLock = utils.locks.AsyncLock.request('numberlock');

console.info("==== 1 " + taskName)

return lock.lockAsync(async () => {

console.info("==== 2 " + taskName)

let nu : number = LazySingletonClass.getInstanceSync().getNum();

console.info("==== " + taskName + " start increase " + nu)

let start = Date.now();

while (Date.now() - start < 100) {

// 模拟等待1秒

}

let increaseNum : number = nu + 5;

LazySingletonClass.getInstanceSync().setNum(increaseNum);

console.info("==== " + taskName + " end increase " + LazySingletonClass.getInstanceSync().getNum())

}, ArkTSUtils.locks.AsyncLockMode.SHARED)

}

单例类如下

import utils from '@arkts.utils';

import { ArkTSUtils } from '@kit.ArkTS';

"use shared"



@Sendable

export class LazySingletonClass {

private static instance: LazySingletonClass;

private num : number = 0;



private constructor() {

}



public static async getInstance(): Promise<LazySingletonClass> {

let lock: utils.locks.AsyncLock = utils.locks.AsyncLock.request('SingletonClass');

return lock.lockAsync(() => {

if (!LazySingletonClass.instance) {

LazySingletonClass.instance = new LazySingletonClass()

}

return LazySingletonClass.instance;

}, ArkTSUtils.locks.AsyncLockMode.EXCLUSIVE)

}



public setNum(num : number) {

this.num = num;

}



public getNum() : number {

return this.num;

}

}

最终输出如下,由于是多线程操作,每次线程输出顺序不尽相同,但是不影响最终数据为15。(如果increaseNumber没有加锁,结果为5)

1

以下是​不加锁时的错误输出​,错误日志每次不同,这里只是截取一次进行说明,可见在increase5和increase7线程中获取到,增长前初始值都是0导致最终输出结果错误。同时由于写线程未加锁,读线程会读取到计算过程中的脏数据,不符合预期;

2

以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
1

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

2

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!
3

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值