前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux上的的Java线程同步机制

Linux上的的Java线程同步机制

原创
作者头像
TimGallin
发布2021-12-03 16:43:18
6080
发布2021-12-03 16:43:18
举报
文章被收录于专栏:thinkythinky

现如今,一个服务端应用程序几乎都会使用到多线程来提升服务性能,而目前服务端还是以linux系统为主。一个多线程的java应用,不管使用了什么样的同步机制,最终都要用JVM执行同步处理,而JVM本身也是linux上的一个进程,那么java应用的线程同步机制,可以说是对操作系统层面的同步机制的上层封装。这里我说的操作系统,主要是的非实时抢占式内核(non-PREEMPT_RT),并不讨论实时抢占式内核(PREEMPT_RT) 的问题,二者由于使用场景不同,因此同步机制也会存在差异或出现变化。

线程同步意指同一个代码块或资源,抢占式内核在调度多个线程时,同时只能允许一个线程访问该资源。也就是编码时要对这部分资源或代码块进行加锁,使得一个线程执行获得锁之后,其他线程无法在获取该锁进而操作相应的资源。内核提供了许多lock机制,但考虑内核调度与进程上下文切换所造成的资源开销浪费,如何在合适的场景使用合适的锁就变得非常重要。

Linux OS的LOCK机制

Linux内核提供的lock原语(locking primitives 指lock方式)大致可以分为三类

CPU local locks

在non-PREEMPT_RT内核上,CPU local locks是基于禁止抢占调度和中断的原语lock机制。当一个进程希望在同一个CPU上持续运行,限制只访问同一个CPU的数据,这时只需要使用local locks,而不需要使用全局锁(global locks)就可以达成这一目的。由于使用场景限制,暂时不讨论这种锁机制。

Sleeping locks

CPU通过把线程状态又TASK_RUNNING状态改为WAIT状态,从而令收到lock保护的关键区同时只能由一个线程访问,而且线程则需要进入WAIT状态直至当前执行关键区代码的线程释放lock,在WAIT期间,线程不再参与任务调度,因此存在上下文切换而导致的开销。mutex,semephore均是常用的Sleeping locks。

Spinning locks

上文中,当一个进程的WAIT时间相对于进程调度的时间很短时会造成性能问题,因为进程可能在频繁的任务调度上耗费大量时间。而Spinning locks则可以有效避免这一问题,当内核执行到Spinning locks关键区时,其他竞争线程不会被置为WAIT状态,而是在一个循环(spin)中保持随时活跃状态,因此不会产生上下文切换或调度的问题。

OS的其他同步操作

除了上述的lock算法实现线程同步,另外操作还提供lock-free的方式实现同步。操作系统中提供一组原子操作指令,例如compare-and-swap,test-and-set,fetch-and-add,read-modify-write。他们都是在一组原子操作中完成对目标内存的读和写动作,以此来防止线程之间的race condition。

CAS

由操作系统提供的一组原子操作用于线程同步,用于实现一些复杂的lock-free或wait-free的算法。

代码语言:txt
复制
function cas(p: pointer to int, old: int, new: int) is
    if *p ≠ old
        return false

    *p ← new

    return true

CAS保证目标int的值一直是最新的,CAS操作必须返回是否执行了写入操作,这可以通过返回一个boolean或者返回目标int的值来实现。线程通过接收这个返回来决定,如果执行失败,通常线程休眠一段时间,所以CAS通常也类似于Spinlock的方式,如果执行成功,则可以视为本次竞争资源成功,可以执行关键区的代码,执行完之后通过set操作,再将目标值还原,在此过程中,由于目标值被本线程成功更新,因此其他线程是不能成功执行CAS操作的。CAS可能由于ABA 问题引起的异常情况。

CAS不会引起用户态与内核态的状态切换,因此效率比Lock要更高。

TAS

Test-and-set,使用原子操作,修改内存值并返回对应内存修改前的值,当一个线程在执行TAS操作时,其他线程不能同时操作对应内存。这与CAS是类似的机制,只是CAS直接比较操作为TRUE时才执行修改操作,同时CAS操作对象至少时32bit的内存,而TAS则可以在bit位上进行操作。

Java应用中的一些同步机制

Java应用层中一些常用的同步机制,一般是对底层lock或lock-free同步机制得一些封装。

AQS

AQS是Java中的一套线程同步框架,依赖于FIFO的等待队列来实现同步或lock机制,对于大多数依赖于一个atomicint来表示状态的同步场景都可以使用AQS框架。Mutex,Semaphore,ReentrantReadWriteLock都利用了AQS框架。

Synchronized

最常用得java同步原语,Jvm通过Object Markword 实现不同场景下的synchronized优化算法。Jvm为每个object关联一个 intrinsic lock(monitor),就是在执行lock操作时,将对应markword复制到线程stack上的lockrecord frame中。 synchronized有如下几种状态

  1. 无锁状态
  2. biased lock状态,线程通过CAS操作获取biased锁,此后markword将记录threadid,之后该线程重复获取lock时,将不再实际执行lock操作。JDK15之后已不再推荐使用biased lock
  3. lightweight状态,通过CAS获取锁,失败时将升级为heavyweight lock
  4. heavyweight状态,重量锁,线程进入WAIT状态

常用的一些同步方法

这些常用的方法通常由于平台或者语言不同,底层有不同的实现。

Semaphore

信号量是一个在多线程间共享的integer变量,通过acquire/release对semaphore进行增减操作,当integer值为0时,进行acquire的线程将进入等待队列,进入WAIT状态。

通常有如下两种类型的semaphore

  1. Counting Semaphores 用于同时有多个线程执行关键取代码,例如控制并发数(例如hystrix的semaphore模式)
  2. Binary Semaphores 和counting类型类似,但counting值只能0或1

Mutex

从语义上来说,Mutex和BianrySemaphore没有太大区别,我们可以用BinarySemaphore实现Mutex,反之亦然。二者的区别主要是在使用方式和场景上,Semaphore是基于Signal机制,而Mutex则是基于Lock机制,mutex主要用于对共享资源的同步保护,lock只能由一个线程拥有。

Conditional Variable

Condition基于Lock使用得条件锁,为线程提供在关键区执行时能够挂起等待知道满足一个条件。通过Lock.newCondition()创建一个新的Condition对象,只能在lock于unlock之间使用。

参阅

1.The Linux Kernel Lock: types and their rules

2.Local locks - Linux kernel 5.8

3.busy waiting

4.Spinlock

5.Mutex vs Semaphore

5.Compare-And-Swap

6.AQS AbstractQueuedSynchronizer

7.TAS Test-and-set

8.Read-Modify-Write

9.Hotspot JVM synchronized implementation

10.Memory Layout of Objects in Java

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Linux OS的LOCK机制
  • OS的其他同步操作
    • CAS
      • TAS
      • Java应用中的一些同步机制
      • AQS
      • Synchronized
      • 常用的一些同步方法
      • 参阅
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档