云阶月地,关锁千重(一.公平和非公平)

本文深入探讨了Java中锁的基本概念及分类,特别是公平锁与非公平锁的区别,并通过生动的例子帮助理解。此外,还提供了ReentrantLock源码级别的解读。

看到文章的标题是不是很诧异,一个搞技术的为什么要搞这么文艺的话题呢?标题说关锁千重,是不是很形象,我们在开发中的锁不也是多种多样么?

Lock

既然之前说了锁千重,那锁到底有多少种,他们的分类又是怎么区分的,为什么这么区分?我来给大家解释一下。

为什么加锁?

面试中有很多时候会问到,为什么加锁?加锁是起到什么作用?

而实际上在我们的开发过程中会出现并发的情况,比如说两个人几乎同时点击了某一个按钮,这个时候就可以简单的理解成并发,那么到底谁先谁后? 程序中就很可能出现错误,当资源出现共享的时候,就会开始涉及到并发了,这个时候我们就可能会用到锁了,来锁住某一个资源,等我用过之后,你才能动。 这就是为什么使用锁。

锁的分类

1.公平锁/非公平锁

2.可重入锁

3.独享锁/共享锁
4.互斥锁/读写锁
5.乐观锁/悲观锁
6.分段锁
7.偏向锁/轻量级锁/重量级锁
8.自旋锁

 

第一次分享,我们就先说这个公平锁和非公平锁。之后会在后序的文章中继续解析!

何为公平?何为非公平?在我们日常生活中的理解不就是对等的就是公平,不对等的就是不公平? 其实差不多的。

公平锁是指多个线程按照申请锁的顺序来获取锁

非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

 

在JAVA的代码中什么是公平锁什么又是非公平的锁呢?

一种是使用Java自带的关键字synchronized对相应的类或者方法以及代码块进行加锁,

而另一种是ReentrantLock,前者只能是非公平锁,而后者是默认非公平但可实现公平的一把锁。

上面的类图看起来很不舒服,因为关于ReentrantLock这个锁,确实是没有很好的配图,我们可以自己画出来理解一下

我们先看非公平锁,我画图大家理解一下,就像公共厕所,不要嫌弃恶心,但是绝对容易理解

上面这幅图,加入说图中的管理员是Lock,然后现在A来了,说我要去厕所,这时候管理员一看,厕所没人,那好你进去把,然后A就进去了。

这个时候WC里面是有A的,正在进行式,这时候B来了,B也想去厕所

但是这个时候WC里面是有A的,然后管理员Lock看了一下,里面有人,排队把。。。

然后这B就得憋着,去进入队列去排队,然后又来了个C,这时候A还在里面,C只能也去排队,

就是这个样子的

这时候又过了一小会来了个D,也想去WC,这时候A恰好结束了,

这时候非公平锁就上场了,Lock管理员一看,里面没人,D你进去把,这时候就是这样的。

然后这时候因为A出来了之后说,我结束了,然后B正准备进去的时候,里面又有了D,这就是出现了非公平锁

而非公平锁的调用就是 A被Lock --> B去wait--> C在B后面wait --> A结束了,unlock -->非公平锁compareAndSetState(0, acquires),里面没人 -->D被Lock 这时候A释放锁lock.release,-->结果晚了一步,没进去,只能继续等着

这就是非公平锁的简单的一种理解,道理其实挺简单的, 而公平锁就不一样了,Lock管理员会告诉新来的D,你前面已经有好几个在排号的了,你想进去,去后边排队。

公平锁的调用是这样子的。

前面其实都是一样的,但是当D来的时候就不一样了,

D来的时候-->lock知道了-->直接调用hasQueuedPredecessors()告诉D有人在排队,你去后边排队,这样子下来就实实在在的保证了公平了。

接下来我们看看ReentrantLock的源代码实现,然后解释一下

public class ReentrantLock implements Lock, java.io.Serializable {        /*同步器提供所有实现机制 */        private final Sync sync;       /**        此锁的同步控制基础。将转换为下面的公平和非公平版本。使用AQS状态表示锁定的保持数。        */       abstract static class Sync extends AbstractQueuedSynchronizer {           private static final long serialVersionUID = -5179523762034025860L;           /**            执行{@link Lock#lock}。子类化x的主要原因是允许非公平版本的快速路径。            */           abstract void lock();           /**            * 执行非公平的tryLock。 tryAcquire在子类中实现,但两者都需要tryf方法的非公平尝试。                注意在这里执行的是非公平锁也就是说在判断方法compareAndSerState的时候,                新的线程可能抢占已经排队的线程的锁的使用权            */           final boolean nonfairTryAcquire(int acquires) {               final Thread current = Thread.currentThread();               int c = getState();               //状态为0,说明当前没有线程占有锁               if (c == 0) {                   if (compareAndSetState(0, acquires)) {                       setExclusiveOwnerThread(current);                       return true;                   }               }               else if (current == getExclusiveOwnerThread()) {                   int nextc = c + acquires;                   if (nextc < 0) // overflow                       throw new Error("Maximum lock count exceeded");                   setState(nextc);                   return true;               }               return false;           }           protected final boolean tryRelease(int releases) {               int c = getState() - releases;               if (Thread.currentThread() != getExclusiveOwnerThread())                   throw new IllegalMonitorStateException();               boolean free = false;               if (c == 0) {                   free = true;                   setExclusiveOwnerThread(null);               }               setState(c);               return free;           }           protected final boolean isHeldExclusively() {               // While we must in general read state before owner,               // we don't need to do so to check if current thread is owner               return getExclusiveOwnerThread() == Thread.currentThread();           }           final ConditionObject newCondition() {               return new ConditionObject();           }           // Methods relayed from outer class           final Thread getOwner() {               return getState() == 0 ? null : getExclusiveOwnerThread();           }           final int getHoldCount() {               return isHeldExclusively() ? getState() : 0;           }           final boolean isLocked() {               return getState() != 0;           }           /**            从流中重构实例(即反序列化它)。            */           private void readObject(java.io.ObjectInputStream s)               throws java.io.IOException, ClassNotFoundException {               s.defaultReadObject();               setState(0); // reset to unlocked state           }       }}}

 

上面的源码都是说的是非公平锁的,我们在看看源码中对公平锁又是怎么进行定义的!

 

    /**        同步对象以进行公平锁定     */    static final class FairSync extends Sync {        private static final long serialVersionUID = -3000897897090466540L;        final void lock() {            acquire(1);        }        /**         tryAcquire的公平版本。除非递归呼叫或没有服务员或是第一个,否则不授予访问权限。            在这里有一个hasQueuedPredecessors的方法,就是这个方法来保证我们不论是新的线程还是已经在进行排队的线程都顺序的去使用锁         */        protected final boolean tryAcquire(int acquires) {            final Thread current = Thread.currentThread();            int c = getState();            if (c == 0) {                if (!hasQueuedPredecessors() &&                    compareAndSetState(0, acquires)) {                    setExclusiveOwnerThread(current);                    return true;                }            }            else if (current == getExclusiveOwnerThread()) {                int nextc = c + acquires;                if (nextc < 0)                    throw new Error("Maximum lock count exceeded");                setState(nextc);                return true;            }            return false;        }    }

解释完了,我们来做个总结 总结一:

1.公平锁:老的线程排队使用锁,新线程仍然排队使用锁。2.非公平锁:老的线程排队使用锁;但是新的线程是极有可能抢占已经在排队的线程的锁。

以后我会更新关于锁的一些文章,希望大家能够指点一下,也能够共同的讨论进步,谢谢!


Java 极客技术公众号,是由一群热爱 Java 开发的技术人组建成立,专注分享原创、高质量的 Java 文章。
如果您觉得我们的文章还不错,请帮忙赞赏、在看、转发支持,鼓励我们分享出更好的文章。

关注公众号,大家可以在公众号后台回复“博客园”,免费获得作者 Java 知识体系/面试必看资料。

 

转载于:https://www.cnblogs.com/justdojava/p/11211838.html

### 三级标题:乐观锁与悲观锁的概念 乐观锁悲观锁是数据库系统中用于处理并发访问控制的两种机制。它们分别适用于不同的并发场景,并对系统的性能数据致性产生不同的影响。 乐观锁假设在并发访问中,数据冲突发生的概率较低。因此,在读取数据时不加锁,而是在提交更新时检查是否有其他事务修改了数据。这种机制通常通过版本号或时间戳实现,适用于读多写少的场景 [^2]。 悲观锁则假设并发冲突经常发生,因此在访问数据时立即加锁,防止其他事务修改数据。这种方式通常依赖数据库的行锁、表锁等机制实现,适用于写操作频繁的场景 [^3]。 ### 三级标题:两者的区别与实现机制 乐观锁的实现方式通常依赖于版本号或时间戳。例如,在更新数据时,数据库会检查当前版本号是否与读取时致,若不致则拒绝更新操作。这种机制避免了锁的开销,提高了并发性能,但在高冲突环境下可能导致大量更新失败 [^2]。 悲观锁的实现方式依赖于数据库的锁机制。例如,在查询数据时使用 `SELECT ... FOR UPDATE` 语句,对数据加排他锁,直到事务提交或回滚后才释放锁。这种方式可以有效防止并发冲突,但可能导致事务阻塞,降低系统并发性能 [^3]。 ### 三级标题:数据库并发控制中的应用 在数据库并发控制中,乐观锁适用于冲突较少的场景,例如电商系统中的商品浏览操作。而悲观锁适用于冲突频繁的场景,例如库存扣减、订单状态更新等关键业务操作 。 以下是个乐观锁的实现示例(基于版本号): ```sql -- 假设表中有个 version 字段 UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 100 AND version = 5; ``` 若其他事务在此期间修改了该记录的 `version`,则当前更新将失败,从而避免数据被错误覆盖。 以下是个悲观锁的实现示例: ```sql -- 在事务中对数据加锁 START TRANSACTION; SELECT * FROM products WHERE id = 100 FOR UPDATE; -- 此时其他事务无法修改该记录 UPDATE products SET stock = stock - 1 WHERE id = 100; COMMIT; ``` 在事务提交前,其他事务对该记录的修改将被阻塞,确保数据致性。 ### 三级标题:总结 乐观锁与悲观锁是数据库并发控制中的两种核心机制。乐观锁通过版本号等方式在提交时检测冲突,适用于低冲突场景,具有较高的并发性能;悲观锁通过加锁机制防止并发访问,适用于高冲突场景,但可能影响系统吞吐量。在实际应用中,应根据业务需求数据访问模式选择合适的并发控制策略 [^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值