synchronized关键字

本文深入探讨了Java中synchronized关键字的底层实现机制,包括monitorenter和monitorexit指令的作用,以及如何通过这些机制实现线程间的同步。此外,文章还详细介绍了分布式系统中的一致性、可用性和分区容错性的权衡,并提出了几种实现分布式锁的方案,包括基于数据库表、缓存和Zookeeper的分布式锁实现,以及每种方案的特点和限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.synchronized 底层实现

synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

2.任何一个分布式系统都不能同时满足一致性,可用性和分区容错性,最多同时满足两项。为保证数据的一致性,需要分布式事务、分布式锁等,有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了很多并发处理相关的API,但是这些API在分布式场景下就无能为力了。

2.1几种方案:①基于数据库表实现分布式锁②基于缓存(redis,memcached,tair)实现分布式锁③基于Zookeeper实现分布锁

2.2分布式锁应该具备哪些条件?①具有互斥性,在分布式系统环境下,只有一个客户端能持有锁;②具有容错性,高可用的获取锁与释放锁。只要大部分的节点正常运行,客户端就可以加锁和解锁 ,解决单点故障;③具有高性能,单位时间获取锁与释放锁的能力;④具备可重入性(主要影响性能),即同一线程在未释放锁时如果再次申请锁资源不需要走申请流程,只需要将已经获取的锁继续返回并记录上已经重入的次数即可;⑤具备锁失效性,防止死锁,即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁;⑥具有阻塞锁特性,阻塞锁即没有获取到锁,则继续等待获取锁;费阻塞锁即没有获取到锁后,不继续等待直接但会锁失败;⑦具备一致性,加锁和解锁必须是同一个客户端,客户端自己不能把别人家的锁给解了。

2.3.1基于数据库表实现分布式锁

实现原理:依靠数据库唯一索引来实现,即多个请求同时插入一条记录只有一条可以插入成功,当想要获取锁时,即向数据库中插入一条记录,释放锁是就删除这条记录。还可以借助数据中自带的排它锁来实现分布式的锁,如基于MySql的InnoDB引擎,在查询语句后面增加for update 如果成功则立刻返回,如果失败则会等待,可以有效地解决上面提到的无法释放锁和非阻塞锁的问题,重入锁则不行。

优点:直接借助数据库,容易实现。

缺点:为非阻塞锁,失败则立刻返回,只能轮询或者返回失败;没有失效机制,需要在表中增加一列,用于记录时效时间,并且需要有定时任务清楚这些失效的数据;不具备可重入的特性,需要在表中新增一列,用于记录当前获取到锁的及其和线程信息;可用性查。单点问题依然存在,数据库需要双机部署,数据同步,主备切换;性能开销大,会有各种各样的问题,在解决问题的过程中会是整个方案编的越来越复杂;使用数据库的行级锁并不一定靠谱,尤其是当我们的锁表并不大的时候;

3.Java内存区域详解

3.1线程私有的:程序计数器、虚拟机栈、本地方法栈;线程共享的:堆,方法区,直接内存(非运行时数据区的一部分)

程序计数器是一块较小的内存空间,可以看作是当前线程锁执行的字节码的行号指示器。字节码解释器工作是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等功能都需要依赖这个计数器来完成。

字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如,顺寻执行,选择,循环,异常处理。

当多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程备切换回来的时候能够知道该线程上次运行到哪里。

程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,他的生命周期随着线程的创建而创建,随着线程的结束而死亡。

Java虚拟机栈与程序计数器一样,Java虚拟机栈也是线程私有的,他的生命周期和线程相同,描述的是Java方法执行的内存模型,每次方法调用的数据都是通过栈传递的。

Java内存可以粗糙的区分为堆内存和栈内存,其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。

虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在HotSpot虚拟机中和Java虚拟机栈合二为一。

堆是所有线程共享的一块内存区域,在虚拟机启动时创建。次内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此也被称作GC堆,从垃圾回收的角度,由于现在手机其基本都采用分带垃圾收集算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值