2024年4月

1、介绍

在日常工作中,肯定会遇到一些重复性的工作,不管是点击某个按钮、写东西,打印东西,还是复制粘贴拷贝资料之类的,需要进行大量的重复操作。按键精灵大家都听说过,传统的方式,大家可以使用按键精灵将操作录制一遍,形成脚本,剩余的工作让计算机自动循环执行,应对这些重复性的任务。

但今天这里给大家分享另外一款鼠标键盘自动化执行操作的神器:KeymouseGo,可以说键盘鼠标的相同重复性的操作这个软件就都能实现。KeymouseGo是一种创新的鼠标键盘一体化设备,它将鼠标和键盘合二为一,通过手指和手腕的动作来控制鼠标和键盘的操作。它可以替代传统的鼠标和键盘,提供更便捷、高效的操作体验。

并且KeyMouseGo免费无广告,界面干净简洁,由Python编写且全开源!

2、特点、优势

KeymouseGo的用途非常广泛,适用于各种使用鼠标和键盘的场景,如办公、设计、游戏等。它可以提高工作效率,减少手部疲劳,提供更自然、流畅的操作方式。

KeymouseGo的特点包括:

  • 一体化设计:将鼠标和键盘合二为一,减少桌面上的设备数量,节省空间。
  • 手指和手腕控制:通过手指和手腕的动作来控制鼠标和键盘的操作,更加灵活方便。
  • 高度可调节:可以根据个人的需求自由调节KeymouseGo的高度和角度,提供更舒适的使用体验。
  • 可编程按键:KeymouseGo的按键可以进行自定义编程,根据个人习惯设置各种快捷操作,提高工作效率。
  • 多平台兼容:KeymouseGo兼容多种操作系统,如Windows、Mac、Linux等,适用于不同的电脑设备。

3、安装

该软件通过 Python 语言编写,已打包为可执行文件,可以文章未尾获取安装包。

官网:

https://github.com/taojy123/KeymouseGo

安装步骤:

1. 安装 Python3
2. pip install -r requirements-windows.txt  # 如果是Linux或Mac,则执行: pip3 install -r requirements-universal.txt
3. pip install pyinstaller
4. pyinstaller -F -w --add-data "./assets;assets" KeymouseGo.py

4、使用

KeyMouseGo软件使用方法很简单,启动后点录制按钮,先自己做一遍要重复的操作。这个操作包括鼠标移动点击、键盘输入等,该软件都是支持记录的。

使用方法:

1、点击 录制 按钮,开始录制。

2、在计算机上进行任意操作,如点击鼠标、键盘输入,这部分的动作会被记录下来。

3、点击 结束 按钮,结束录制。

4、点击 启动 按钮,计算机会重复执行一遍第2步中所录制的动作。

接运行指定脚本:

> ./KeymouseGo scripts/0314_1452.txt

运行指定脚本3次:

> ./KeymouseGo scripts/0314_1452.txt -rt 3
> ./KeymouseGo scripts/0314_1452.txt --runtimes 3

以200%的速度运行指定脚本:

> ./KeymouseGo scripts/0314_1452.txt -sp 200
> ./KeymouseGo scripts/0314_1452.txt --speed 200

5、注意事项

  • 1、可设置脚本重复执行的次数,如果为 0 即为无限循环。

  • 2、默认启动热键为 F6,功能等同于 启动 按钮;默认终止热键为 F9,按下后将会停止正在运行的脚本。

  • 3、录制时只记录鼠标点击动作和键盘动作,不记录鼠标移动轨迹。

  • 4、每次录制结束后都会在 scripts 目前下生成一个新的脚本文件。

  • 5、运行前可以在列表中选择一个需要执行的脚本。

  • 6、scripts 下的脚本文件内容可以修改,修改时可参考如下所述 脚本格式说明。

  • 7、部分系统环境中,可能出现无法录制完整的鼠标事件的情况,请以管理员身份身份运行此工具即可正常使用。

最近在用next写一个多语言的项目,找了好久没找到简单实现的教程,实践起来感觉都比较复杂,最后终于是在官方文档找到了,结合网上找到的代码demo,终于实现了,在这里简单总结一下。

此教程适用于比较简单的项目实现,如果你是刚入门next,并且不想用太复杂的方式去实现一个多语言项目,那么这个教程就挺适合你的。

此教程适用于app目录的next项目。

先贴一下参阅的连接:

官方教程: next i18n 文档

可参阅的代码demo

实现思路

结合文件结构解说一下大致逻辑:

image

  • i18n-config.ts
    只是一个全局管理多语言简写的
    枚举
    文件,其他文件可以引用这个文件,这样就不会出现不同文件对不上的情况。
  • middleware.ts
    做了一层拦截,在用户访问
    localhost:3000
    的时候能通过请求头判断用户常用的语言,配合app目录多出来的
    [lang]
    目录,从而实现跳转到
    localhost:3000/zh
    这样。
  • dictionaries
    文件夹下放各语言的json字段,通过字段的引用使页面呈现不同的语种。
    事实上每个页面的
    layout.tsx

    page.tsx
    都会将语言作为参数传入,在对应的文件里,再调用
    get-dictionaries.ts
    文件里的方法就能读取到对应的json文件里的内容了。

大致思路是这样,下面贴对应的代码。

/i18n-config.ts

export const i18n = {
    defaultLocale: "en",
    // locales: ["en", "zh", "es", "hu", "pl"],
    locales: ["en", "zh"],
} as const;

export type Locale = (typeof i18n)["locales"][number];

/middleware.ts
,需要先安装两个依赖,这两个依赖用于判断用户常用的语言:

npm install @formatjs/intl-localematcher
npm install negotiator

然后才是
/middleware.ts
的代码:

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

import { i18n } from "./i18n-config";

import { match as matchLocale } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";

function getLocale(request: NextRequest): string | undefined {
  // Negotiator expects plain object so we need to transform headers
  const negotiatorHeaders: Record<string, string> = {};
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));

  // @ts-ignore locales are readonly
  const locales: string[] = i18n.locales;

  // Use negotiator and intl-localematcher to get best locale
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages(
    locales,
  );

  const locale = matchLocale(languages, locales, i18n.defaultLocale);

  return locale;
}

export function middleware(request: NextRequest) {
  const pathname = request.nextUrl.pathname;

  // // `/_next/` and `/api/` are ignored by the watcher, but we need to ignore files in `public` manually.
  // // If you have one
  // if (
  //   [
  //     '/manifest.json',
  //     '/favicon.ico',
  //     // Your other files in `public`
  //   ].includes(pathname)
  // )
  //   return

  // Check if there is any supported locale in the pathname
  const pathnameIsMissingLocale = i18n.locales.every(
    (locale) =>
      !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`,
  );

  // Redirect if there is no locale
  if (pathnameIsMissingLocale) {
    const locale = getLocale(request);

    // e.g. incoming request is /products
    // The new URL is now /en-US/products
    return NextResponse.redirect(
      new URL(
        `/${locale}${pathname.startsWith("/") ? "" : "/"}${pathname}`,
        request.url,
      ),
    );
  }
}

export const config = {
  // Matcher ignoring `/_next/` and `/api/`
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

/dictionaries
下的因项目而异,可以看个参考:
image

文件以语言简写命名,
/i18n-config.ts
里的
locales
有什么语言,这里就有多少个对应的文件就行了。

/get-dictionaries.ts

import "server-only";
import type { Locale } from "./i18n-config";

// We enumerate all dictionaries here for better linting and typescript support
// We also get the default import for cleaner types
const dictionaries = {
  en: () => import("./dictionaries/en.json").then((module) => module.default),
  zh: () => import("./dictionaries/zh.json").then((module) => module.default),
};

export const getDictionary = async (locale: Locale) => dictionaries[locale]?.() ?? dictionaries.en();

实际使用可以做个参考:
image

到这里其实就实现了,但是下面的事情需要注意:

如果你的项目有集成了第三方需要配知道middleware的地方,比如clerk,需要调试一下是否冲突。

如果你不知道clerk是什么,那么下面可以不用看,下面将以clerk为例,描述一下可能遇到的问题和解决方案。

Clerk适配

clerk是一个可以快速登录的第三方库,用这个库可以快速实现用户登录的逻辑,包括Google、GitHub、邮箱等的登录。

clerk允许你配置哪些页面是公开的,哪些页面是需要登录之后才能看的,如果用户没登录,但是却访问了需要登录的页面,就会返回401,跳转到登录页面。

就是这里冲突了,因为我们实现多语言的逻辑是,用户访问
localhost:3000
的时候判断用户常用的语言,从而实现跳转到
localhost:3000/zh
这样。

这两者实现都在
middleware.ts
文件中,上面这种配置会有冲突,这两者只有一个能正常跑通,而我们想要的效果是两者都能跑通,既能自动跳转到登录页面,也能自动跳转到常用语言页面。

技术问题定位:这是因为你重写了middleware方法,导致不会执行Clerk的authMiddleware方法,视觉效果上,就是多语言导致了Clerk不会自动跳转登录。

所以要把上面的middleware方法写到authMiddleware方法里的beforeAuth里去,Clerk官方有说明:
Clerk authMiddleware说明

image

所以现在/middleware.ts文件内的内容变成了:

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { authMiddleware } from "@clerk/nextjs";
import { i18n } from "./i18n-config";
import { match as matchLocale } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";

function getLocale(request: NextRequest): string | undefined {
  // Negotiator expects plain object so we need to transform headers
  const negotiatorHeaders: Record<string, string> = {};
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));

  // @ts-ignore locales are readonly
  const locales: string[] = i18n.locales;

  // Use negotiator and intl-localematcher to get best locale
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages(
    locales,
  );

  const locale = matchLocale(languages, locales, i18n.defaultLocale);

  return locale;
}

export const config = {
  // Matcher ignoring `/_next/` and `/api/`
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
  // matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
};

export default authMiddleware({
  publicRoutes: ['/anyone-can-visit-this-route'],
  ignoredRoutes: ['/no-auth-in-this-route'],
  beforeAuth: (request) => {
    const pathname = request.nextUrl.pathname;

    // // `/_next/` and `/api/` are ignored by the watcher, but we need to ignore files in `public` manually.
    // // If you have one
    if (
      [
        '/manifest.json',
        '/favicon.ico',
        '/serviceWorker.js',
        '/en/sign-in'
        // Your other files in `public`
      ].includes(pathname)
    )
      return

    // Check if there is any supported locale in the pathname
    const pathnameIsMissingLocale = i18n.locales.every(
      (locale) =>
        !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`,
    );

    // Redirect if there is no locale
    if (pathnameIsMissingLocale) {
      const locale = getLocale(request);

      // e.g. incoming request is /products
      // The new URL is now /en-US/products
      return NextResponse.redirect(
        new URL(
          `/${locale}${pathname.startsWith("/") ? "" : "/"}${pathname}`,
          request.url,
        ),
      );
    }
  }
});

这样就OK了,大功告成。

1.问题

image
微信开放社区链接

尝试过新版canvas,在
企业微信
中签名依然是依然断触,有问题的手机是iphoe15,系统版本以及企微版本微信版本均与签名正常的手机一致,但是那个手机就是无法正常签字,在微信中无论新旧canvas均能正常签字

2.解决方案

既然canvas的touchmove触发有问题,那么就可以通过
替代canvas的touchmove
来实现,通过在canvas上覆盖一层dom,通过这层dom的touchmove来获取手指划过的轨迹即可,此文章中并没有小程序实际代码只是使用了h5验证可行性的代码

2.1 注意点
  • 要区别手指是否连续滑动,由于点击事件触发存在如下情况

区别手指是否连续滑动采用时间间隔判断
触发事件间隔小于80ms 主要用于判断是否松开手指再次滑动,正常手速来说80ms,人很难在画完一个线段后,松手再次画一个线段,如果无这个处理会出现滑动一个线段之后,再次点击另一个点会把线段和新点位连接起来

没有采取通过touchstart与touchend做一个判断是因为touchmove并不是固定一直在start与end事件中间触发
直接点击触发事件顺序

2.2
移动端浏览器体验地址
2.2 vue2代码
<template>
  <div class="DomCanvasSignature">
    <div :style="{ height: height + 'px', width: width + 'px' }" class="signatureWrapper" id="signatureWrapper"
      draggable="false" @mousedown="touchstart" @mouseup="touchend" @touchstart="touchstart" @touchend="touchend"
      @touchmove="touchmove" @mousemove="touchmove">
      <canvas canvas-id="999" :height="height" :width="width - 3" class="canvas" />
    </div>
  </div>
</template>
<script>
export default {
  name: 'DomCanvasSignature',
  data () {
    return {
      height: 302,
      width: 302,

      mycanvas: null,
      previousPoint: {
        x: 0,
        y: 0
      },
      isPcStart: false,
      removeLisner: () => { }
    }
  },


  methods: {
    initSize () {
      this.width = window.innerWidth
      this.height = window.innerHeight - 300
    },
    lisner () {
      this.initSize()
      window.addEventListener('resize', this.initSize)
      return () => {
        window.removeEventListener('resize', this.initSize)
      }
    },
    touchstart () {
      this.isPcStart = true
      console.log('====start') // zdz-log
    },
    touchend () {
      this.isPcStart = false
      console.log('====end') // zdz-log

    },
    touchmove (e) {
      console.log('move', e) // zdz-log
      // 阻止滚动
      e.preventDefault()
      if (e.type === 'mousemove' && !this.isPcStart) {
        return
      }
      // 合并处理 pc 与移动端
      const changeObj = e.changedTouches && e.changedTouches[0] || e
      const current = { x: changeObj.clientX, y: changeObj.clientY, timeStamp: e.timeStamp }

      // 1.获取元素
      // 2.获取上下文,绘制工具箱
      let ctx = this.mycanvas.getContext('2d')
      // 3.移动画笔
      const currentY = (current.y) - signatureWrapper.offsetTop
      // todo 改为touchstart 与end判断 无法实现 因为move 执行存在在 start end事件之后
      let diffLarge = false
      console.log(current.timeStamp - this.previousPoint.timeStamp) // zdz-log
      // 判断是否松手重新绘制
      if (this.previousPoint.timeStamp) {
        const timeDiff = current.timeStamp - this.previousPoint.timeStamp > 80
        if (timeDiff) {
          diffLarge = true
        }
      }

      const preY = diffLarge ? current.y - signatureWrapper.offsetTop : (this.previousPoint.y || current.y) - signatureWrapper.offsetTop
      const moveX = diffLarge ? current.x : this.previousPoint.x || current.x
      ctx.moveTo(moveX, preY < 0 ? 0 : preY)
      // 4.绘制直线(轨迹,绘制路径)
      ctx.lineTo(current.x, currentY < 0 ? 0 : currentY)
      // 5.描边
      ctx.stroke()

      this.previousPoint = current

    },


  },
  created () {
    this.removeLisner = this.lisner()
  },
  destroyed () {
    this.removeLisner()
  },
  mounted () {
    this.mycanvas = document.querySelector('canvas')
    this.signatureWrapper = document.getElementById('signatureWrapper')
  },

}
</script>

<style scoped>
.canvas {
  border: 1px solid red;
}

.signatureWrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid black;
  background-color: transparent;
}
</style>


随着人工智能技术的飞速发展,.Net技术与AI的结合已经成为了一个新的技术热点。今天,我要和大家分享一个令人兴奋的开源项目——
AntSK
,这是一个基于.net平台构建的
开源离线
AI知识库项目。在这个项目中,我们最近加入了一项强大的
Rerank
(重排)模型,进一步增强了我们的AI知识库的查询能力。如果你关心.NET和AI的融合应用,那么请继续阅读,我保证这会是一次充满技术洞见的探索旅程。

引言

在现代搜索引擎技术中,如何从庞大的数据中快速、准确地找到用户需要的信息,这一点至关重要。虽然向量匹配技术已经可以实现语义相关度的匹配,它是根据向量之间的距离来判断文档与查询之间的相关性,但这往往不能满足我们日益增长的精准匹配需求。文档质量的参差不齐,以及用户查询意图的多样性,让向量匹配技术看起来有些力不从心。

AntSK知识库项目正是针对这些挑战而诞生,目标是构建一个强大、灵活且易于扩展的AI知识库框架。引入
Rerank
技术后,AntSK可以对向量匹配后的初步结果进行进一步的重排序,从而显著提升搜索结果的相关性和质量。

AntSK项目简介

AntSK是一个开源且强大的AI知识库框架,它集成了先进的语义理解技术。通过结合

semantic kernel和kernel memory,它提供了一个扩展的AI功能平台,还支持Python混合编程,让.NET开发者也可以轻松利用Python丰富的AI生态库进行开发。

项目地址参见:

https://github.com/AIDotNet/AntSK

Rerank模型的介绍及应用

要在AntSK中加入Rerank模型,我们可以使用一个名为FlagEmbedding的开源项目作为参考。FlagEmbedding提供了一个用于文档重排序的优秀实践,它可以通过更复杂的模型和更多因素,例如文档的语义深度、用户查询意图等,来筛选出更加配准的结果。

FlagEmbedding项目地址:

https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/llm_reranker#model-list

在AntSK中,我们需要通过
pythonnet
来运行Rerank模型,这样可确保.NET和Python之间的无缝集成。由于网络原因,一些国内用户在下载模型时可能会遇到困难,因此我们对下载部分进行了修改,使其支持从modelscope进行下载。

接下来,我们来看看如何在AntSK中实现ReRank模型的集成。首先建立一个pythonnet类,以下是一个简化版的代码实现:

public static class BegRerankConfig
{
    public static dynamic model { get; set; }

    static object lockobj = new object();



    /// <summary>
    /// 模型写死
    /// </summary>
    public static dynamic LoadModel(string pythondllPath, string modelName)
    {
        lock (lockobj)
        {
            if (model == null)
            {
                if (string.IsNullOrEmpty(Runtime.PythonDLL))
                {
                    Runtime.PythonDLL = pythondllPath;
                }
                PythonEngine.Initialize();
                try
                {
                    using (GIL())// 初始化Python环境的Global Interpreter Lock)
                    {
                        dynamic modelscope = Py.Import("modelscope");
                        dynamic flagEmbedding = Py.Import("FlagEmbedding");

                        dynamic model_dir = modelscope.snapshot_download(modelName, revision: "master");
                        dynamic flagReranker = flagEmbedding.FlagReranker(model_dir, use_fp16: true);
                        model = flagReranker;
                        return model;
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
            else
            {
                return model;
            }
        }
    }


    public static double Rerank(List<string> list)
    {
        using (GIL())
        {
            try
            {
                PyList pyList = new PyList();
                foreach (string item in list)
                {
                    pyList.Append(item.ToPython()); // 将C# string转换为Python对象并添加到PyList中
                }
                PyObject result = model.compute_score(pyList, normalize: true);
                return result.As<double>();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}

代码详见AntSK项目。通过简单的集成,我们就能为AntSK赋予ReRank的能力。另外,我们将初步查找的
top 5
结果扩展为
top 20,甚至更多
,然后通过ReRank模型对这些结果进一步筛选和排序,最后只抽取重排后的
top 5
。这样的操作流程大大提升了结果的准确性。

为什么需要向量匹配后再Rerank?

这是因为Rerank依赖的是一对一的精确匹配模型,在处理海量文档时效率较低。因此,将向量匹配作为第一轮粗筛选择,然后让Rerank技术进行第二轮精细的筛选排序,就显得格外高效且合理。

效能测试

经过初步的测试,引入Rerank技术后,AntSK在搜索结果的相关性和准确性上都得到了显著的提升。这不仅加深了我们对混合AI系统的认识,也为.NET平台上的AI应用开发提供了新的可能性和方向。

结语

通过AntSK这一案例,我们可以明显看到.NET和AI领域融合的趋势。随着技术的不断进步,我们预计会有越来越多精彩的.NET结合AI的应用场景出现。现在,你可以通过访问Github上的AntSK项目,深入了解这一先进技术,并将其应用于你自己的工作当中。

本文仅是一个起点,探讨AntSK知识库框架以及Rerank在内的相关深度技术。我将继续关注这一个领域的进展,并与大家分享更多精彩内容。感谢阅读,让我们一起期待.NET/AI的融合未来!

另外也欢迎大家加入我们的社区交流群,关注公众号《许泽宇的技术分享》发送进群!
这是一个非常和谐的社区,大家在里面讨论AI技术,非常融洽。

写在开头

随手一翻,发现对于Java中并发多线程的学习已经发布了十几篇博客了,
多线程
是Java基础中的重中之重!因此,可能还需要十几篇博客才能大致的讲完这部分的知识点,初学者对于这部分内容一定要多花心思,不可马虎!今天我们继续来学习一个重要知识点:
ReentrantLock

ReentrantLock
:是一种独占式的可重入锁,位于java.util.concurrent.locks中,是Lock接口的默认实现类,底部的同步特性基于AQS实现,和synchronized关键字类似,但更灵活、功能更强大、也是目前实战中使用频率非常高的同步类。

几种不同锁的定义

在学习ReentrantLock之前,我们先来复习一下如下的几类锁的定义,这个其实很早的博文中就已经详细的整理过了,这里为了更好理解ReentrantLock锁,还是简单罗列一下。

独占锁与共享锁

  1. 独占锁:同一时间,一把锁只能被一个线程获取;
  2. 共享锁:同意时间,一把锁可以被多个线程获取。

公平锁与非公平锁

  1. 公平锁:按照申请锁的时间先后,进行锁的再分配工作,这种锁往往性能稍差,因为要保证申请时间上的顺序性;
  2. 非公平锁: 锁被释放后,后续线程获得锁的可能性随机,或者按照设置的优先级进行抢占式获取锁。

可重入锁

所谓可重入锁就是一个线程在获取到了一个对象锁后,线程内部再次获取该锁,依旧可以获得,即便持有的锁还没释放,仍然可以获得,不可重入锁这种情况下会发生死锁!

可重入锁在使用时需要注意的是:
由于锁会被获取 n 次,那么只有锁在被释放同样的 n 次之后,该锁才算是完全释放成功。

可中断锁与不可中断锁

  1. 可中断锁:在获取锁的过程中可以中断获取,不需要非得等到获取锁后再去执行其他逻辑;
  2. 不可中断锁:一旦线程申请了锁,就必须等待获取锁后方能执行其他的逻辑处理。


ReentrantLock是一种同时拥有独占式、可重入、可中断、公平/非公平特性的同步器!


ReentrantLock

根据上面总结出的特点,我们从底层源码出发来验证一下结论的准确性,首先我们通过一个关系图谱来大致梳理一下ReentrantLock的内部构造。

image

ReentrantLock实现了Lock和Serializable接口:

public class ReentrantLock implements Lock, java.io.Serializable {}

其内部拥有三个内部类,分别为Sync、FairSync、NonfariSync,其中FairSync、NonfariSync继承父类Sync。Sync又继承了AQS(AbstractQueuedSynchronizer),添加锁和释放锁的大部分操作实际上都是在 Sync 中实现的。

问题1:ReentrantLock内部公平锁与非公平锁如何实现?

在内部通过构造器来实现公平锁与非公平锁的设置,默认为非公平锁,同样可以通过传参设置为公平锁。底层实现其实是通过FairSync、NonfariSync这个两个内部类,源码如下:

//无参构造,默认为非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
// 传入一个 boolean 值,true 时为公平锁,false 时为非公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

问题2:独占锁如何实现?

在源码中无论是Sync这个内部类或是其子类,都会调用setExclusiveOwnerThread(current)这个方法,这个方法是AQS的父类AOS(AbstractOwnableSynchronizer)中的方法,用以标记锁的持有者为独占模式。

image

问题3:ReentrantLock如何获取和释放锁?

由于ReentrantLock是默认非公平锁,所以我们就以非公平模式为例去看一下它底层如何实现锁的获取与释放的。

1️⃣
锁的获取

核心方法为Sync内部类的nonfairTryAcquire方法,如下为其源码,先获取当前锁的状态,若为0说明没有被任何线程获取,此时直接获取即可;另外一种state不为0时,则需要判断占有线程是否为当前线程,若是则可以获取,并将state值加一返回,否则获取失败。

【注意】
:公平模式下获取锁的时会多一步调用hasQueuedPredecessors 的逻辑判断,用以判断当前线程对应的节点在等待队列中是否有前驱节点,毕竟公平锁的竞争严格按照获取锁的时间进行分配的。

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //1. 如果该锁未被任何线程占有,该锁能被当前线程获取
	if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
	//2.若被占有,检查占有线程是否是当前线程
    else if (current == getExclusiveOwnerThread()) {
		// 3. 再次获取,计数加一
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

2️⃣
锁的释放

对应的以非公平锁中释放为例,通过源码我们可以看到,每调用一次则同步状态减1,直至同步状态为0,锁才被完全的释放完,否则返回false。

protected final boolean tryRelease(int releases) {
	//1. 同步状态减1
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
		//2. 只有当同步状态为0时,锁成功被释放,返回true
        free = true;
        setExclusiveOwnerThread(null);
    }
	// 3. 锁未被完全释放,返回false
    setState(c);
    return free;
}

3️⃣
小总结

经过上面源码的学习,我们已经能够确认一点就是:ReentrantLock是一种同时拥有独占式、可重入、可中断、公平/非公平特性的同步器!我们接下来就继续再来学习一下它的使用。

问题4:ReentrantLock的使用

我们通过一个小demo,来感受一下基于非公平锁模式下的ReentrantLock的使用哈

public class Test {
    //初始化一个静态lock对象
    private static final ReentrantLock lock = new ReentrantLock();
    //初始化计算量值
    private static int count;

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            for (int i = 0; i <1000 ; i++) {
                lock.lock();
                try {
                    count++;
                } finally {
                    lock.unlock();
                }
            }
        });
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 1000; i++) {
                lock.lock();
                try {
                    count++;
                } finally {
                    lock.unlock();
                }
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("result:"+count);
    }
}

上面这个程序预期输出结果为:2000,thread1和thread2分别做了加1000次的操作,由于ReentrantLock是独占式可重入锁,故最终可以成功打印出预期结果!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得
留言+点赞+收藏
呀。原创不易,转载请联系Build哥!

image

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

image