终于把Java锁分类理清楚了

开发 前端
锁,是排他的一种机制,排他性是锁的统一标准,而根据其排他的积极程度,分为乐观锁和悲观锁,而悲观锁又分为两大派系,一个是以synchronized为首的老派锁,一个是以AQS为首的新派锁。
在Java中,锁的应用是非常非常常见的,它是程序抵御高并发访问的防御组件,是Java开发过程中离不开的依赖,在Java发展过程中,锁有了越来越多的适用不同应用场景的分支锁,根据不同的特性可以有不同的分类及具体的实现,下面就不同的分类标准说明下Java中有哪些类型的锁。

1.内置锁与显式锁

开发人员根据书写方式将锁分为内置锁和显式锁,这个分类纯粹是因为Java中的锁分为两大派:老派锁和新派锁。synchronized一人开一派,曾独占鳌头数十载,是老派锁的代表,新派则是以AQS为首新兴门派,其子子孙孙遍布并发编程的各个角落。

synchronized在书写上只需要在方法名或者静态代码块中声明synchronized关键字即可,其加锁,释放锁均是由程序自行处理,因而得名内置锁。

而新派锁AQS门下的锁基本都是需要手动声明加锁和释放锁的,如果程序员忘记写释放锁的代码就会造成锁永远不会释放,其门下应用最多且与synchronized能对等换的当属如ReentrantLock,因为有了显式锁的说法。

内置锁不需要程序员操心上锁和释放锁的逻辑,但是在应用灵活度上有些欠缺,比如说控制锁的范围,内置锁就不能很好的缩小锁范围。

显示锁因为其需要手动加锁和释放锁,所以具备更高的控制加锁范围的灵活性。因为其锁的实现是在Java层面实现,作为Java开发者可以灵活优化其功能,所以其功能可以更多,而synchronized是由jvm层面实现,作为Java开发者就没有办法修改到jvm底层所以功能上会有局限性。

2. 可重入锁与不可重入锁

我们知道当前线程获取到锁后,另外的线程就获取不到这把锁,那么当前线程如果再次获取这把锁的时候,是否能获取到呢,如果能获取到就是可重入锁,如果获取不到就是不可重入锁,即是否可重入说的是线程自己是否能够多次获取锁。

这里需要注意的是可重入锁,重入几次,释放锁的时候也要释放几次,synchronized和ReentrantLock是可重入锁,且新派锁中作为aqs的子子孙孙很容易实现不可重入的锁,程序员也可以手动根据可重入锁实现不可重入锁。但是在Java中基本没有提供现成的不可重入锁实现类。

3. 公平锁与非公平锁

顾名思义,公平不公平就是大家是否有秩序的去竞争锁资源,何为有秩序,那就是排队呗,讲究先来后到。

老派锁的代表synchronized是一个实打实的非公平锁,它的非公平是说没有抢到锁的线程都会被阻塞并放到某个集合中,这个集合是jvm底层实现,我们暂不关心,当占有锁的线程释放锁后,jvm会在这个集合中随机唤醒一个去获取锁,这里的非公平重在随机。

我们说了新派锁是基于AQS,所以其子孙后代锁在功能上会有更高的灵活性。以ReentrantLock为例,它默认情况下是非公平锁,但是可以通过传参实现公平锁。其非公平和老派锁有点不一样,例如,线程1获取了锁后,线程2 线程3因为获取不到锁而进入阻塞队列进行排队阻塞,当有线程4试图获取锁资源的时候,如果声明的是公平锁,线程4会直接进入队列排队,如果声明的是非公平锁,线程4会直接抢锁,即这里的非公平说的是新进来的线程与阻塞队列头部的线程争夺锁资源,强调的是新线程与队列老大的竞争。

新老派锁的非公平都会不同成都的造成,某些或则某个线程一直拿不到锁,但是似乎老派锁synchronized造成这种现象的概率大一点。

4.共享锁与独占锁

既然是锁,那一定是某些线程不能进入,但是哪些线程能进入,就造就了共享锁和独占锁的区分,顾名思义,只允许一个线程独享的锁就是独占锁,允许多个线程共同进入的锁就是共享锁。

老派锁synchronized和新派锁ReentrantLock都是独占锁,而新派锁AQS门下,有一些锁是共享锁,例如:ReentrantReadWriteLock,Semaphore,CountDownLatch,CyclicBarrier等

5. 乐观锁与悲观锁

锁的作用就是将某段业务逻辑保护起来,更深一点就是将逻辑中共享数据保护起来,防止多个线程操作共享数据的时候出现问题。然而开发者根据锁的情感态度将锁分为乐观锁和悲观锁,我们上面说的老派锁和新派锁,他们都对共享数据的保护持悲观态度,他们认为所有的线程都有可能侵害共享数据,因此早早在业务逻辑前加上锁,每来一个线程都要进行排查。并且只允许一个线程进入。所以他们都是悲观锁。

然而就在新派锁崛起的同一时期,遥远的北方有一支异军突起,他们剑走偏锋,他们认为所有人在没有露出魔鬼爪牙前都是好人,他们持一种乐观的态度对待共享数据的保护,但是他们并不是不保护,只是保护的方式是,当某个线程对共享数据操作前,必须先获取一个令牌,然后拿着令牌去操作共享数据,当共享数据保存的令牌与整个线程持有的令牌匹配正确后才能操作。如果匹配不上就操作失败。

在操作数据失败后应该怎么操作,就需要开发者自行处理,或者直接返回失败信息,或者再次重试,一般情况下,乐观锁会结合自旋重试来使用,但是当并发很高的时候,会造成很多线程自旋,给cpu带来压力,所以适用的场景有限。

总结

锁,是排他的一种机制,排他性是锁的统一标准,而根据其排他的积极程度,分为乐观锁和悲观锁,而悲观锁又分为两大派系,一个是以synchronized为首的老派锁,一个是以AQS为首的新派锁。

而公平与非公平,可重入与不可重入,共享与独占,内置与显式,可中断与不可中断都是锁的特性。

接下来就分开来介绍其底层实现。

责任编辑:武晓燕 来源: 码农本农
相关推荐

2020-07-29 09:21:34

Docker集群部署隔离环境

2020-03-02 15:17:37

云原生CNCF容器

2019-07-04 09:13:04

中台百度团队

2021-02-25 08:21:38

高可用风险故障

2018-07-26 09:06:29

Java内存模型

2020-10-29 10:35:53

Nginx架构服务器

2024-04-01 10:09:23

AutowiredSpring容器

2019-10-21 08:51:41

分布式事务CAPAP

2021-04-10 10:37:04

OSITCP互联网

2022-11-11 15:49:41

MySQL隔离

2020-01-13 15:34:10

超融合边缘计算架构

2022-11-16 14:02:44

2022-01-04 09:53:37

Python多线程多进程

2024-02-27 14:27:16

2021-07-05 22:22:24

协议MQTT

2019-07-07 08:18:10

MySQL索引数据库

2020-11-16 08:37:16

MariaDB性能优化

2022-01-05 09:27:24

读扩散写扩散feed

2019-05-22 08:43:45

指令集RISC-V开源

2021-06-13 12:03:46

SaaS软件即服务
点赞
收藏

51CTO技术栈公众号