记录二:公平锁和非公平锁

目录

公平锁和非公平锁概念

案例演示

为何默认非公平锁

应用场景


公平锁和非公平锁的概念

公平锁:是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,这是公平的。如:

Lock lock = new Reentrant(true); //ture表示公平锁,先来先得。

非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或者饥饿的状态(某一个线程一直得不到锁)。如:

Lock lock = new ReentrantLock(false); //false 表示非公平锁,后来的也可能先获得锁
Lock lock = new ReentrantLock();//默认非公平锁

案例演示


import java.util.concurrent.locks.ReentrantLock;

class Ticket {//资源类,模拟三个售票员买完30张票
    private int number = 30;
    ReentrantLock lock = new ReentrantLock(true);//true 公平锁 ,默认false 非公平锁

    public void sale() {
        lock.lock();
        try {
            if(number > 0){
                System.out.println(Thread.currentThread().getName()+ "卖出第:\t" + number-- +"\t 还剩下:" + number);
            }
        } finally {
            lock.unlock();
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 55; i++) {
                ticket.sale();
            }
        }, "t1").start();
        new Thread(() -> {
            for (int i = 0; i < 55; i++) {
                ticket.sale();
            }
        }, "t2").start();
        new Thread(() -> {
            for (int i = 0; i < 55; i++) {
                ticket.sale();
            }
        }, "t3").start();
    }
}

公平锁结果集:

t1卖出第:	30	 还剩下:29
t2卖出第:	29	 还剩下:28
t1卖出第:	28	 还剩下:27
t2卖出第:	27	 还剩下:26
t1卖出第:	26	 还剩下:25
t3卖出第:	25	 还剩下:24
t2卖出第:	24	 还剩下:23
t1卖出第:	23	 还剩下:22
t3卖出第:	22	 还剩下:21
t2卖出第:	21	 还剩下:20
t1卖出第:	20	 还剩下:19
t3卖出第:	19	 还剩下:18
t2卖出第:	18	 还剩下:17
t1卖出第:	17	 还剩下:16
t3卖出第:	16	 还剩下:15
t2卖出第:	15	 还剩下:14
t1卖出第:	14	 还剩下:13
t3卖出第:	13	 还剩下:12
t2卖出第:	12	 还剩下:11
t1卖出第:	11	 还剩下:10
t3卖出第:	10	 还剩下:9
t2卖出第:	9	 还剩下:8
t1卖出第:	8	 还剩下:7
t3卖出第:	7	 还剩下:6
t2卖出第:	6	 还剩下:5
t1卖出第:	5	 还剩下:4
t3卖出第:	4	 还剩下:3
t2卖出第:	3	 还剩下:2
t1卖出第:	2	 还剩下:1
t3卖出第:	1	 还剩下:0

Process finished with exit code 0

非公平锁结果集:

t3卖出第:	30	 还剩下:29
t3卖出第:	29	 还剩下:28
t3卖出第:	28	 还剩下:27
t3卖出第:	27	 还剩下:26
t3卖出第:	26	 还剩下:25
t3卖出第:	25	 还剩下:24
t3卖出第:	24	 还剩下:23
t3卖出第:	23	 还剩下:22
t3卖出第:	22	 还剩下:21
t3卖出第:	21	 还剩下:20
t3卖出第:	20	 还剩下:19
t3卖出第:	19	 还剩下:18
t3卖出第:	18	 还剩下:17
t3卖出第:	17	 还剩下:16
t1卖出第:	16	 还剩下:15
t1卖出第:	15	 还剩下:14
t1卖出第:	14	 还剩下:13
t1卖出第:	13	 还剩下:12
t1卖出第:	12	 还剩下:11
t1卖出第:	11	 还剩下:10
t1卖出第:	10	 还剩下:9
t1卖出第:	9	 还剩下:8
t1卖出第:	8	 还剩下:7
t1卖出第:	7	 还剩下:6
t1卖出第:	6	 还剩下:5
t1卖出第:	5	 还剩下:4
t1卖出第:	4	 还剩下:3
t1卖出第:	3	 还剩下:2
t1卖出第:	2	 还剩下:1
t1卖出第:	1	 还剩下:0

Process finished with exit code 0

结论:可以看到公平锁3个线程相对都可以获取到资源,非公平锁只有2个线程有获取到资源,也可能有1个或者3个线程都能获取到资源。

为何默认非公平锁

1.恢复挂起的线程到获取到真正的锁还是有时间差的,从开发人员看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU的时间片,尽量减少CPU空闲状态时间。

2.使用多线程很重要的考量点线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,那么刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

应用场景

非公平锁:如果系统为了获得更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间的,吞吐量自然就上去了;

公平锁:承上 否则那就用公平锁,大家公平使用

另外思考下

1.synchronized 是公平锁还是非公平锁呢?案例说明是非公平锁

 class SaleShop {
    //获取食物
    public void goShop() {
        System.out.println(Thread.currentThread().getName()+":排队中");
        synchronized (this) {
            System.out.println(Thread.currentThread().getName()+":买东西");
        }
    }
}
public class SyncLockTest {
    public static void main(String[] args) {
        SaleShop saleShop = new SaleShop();
        //让5个人去购买东东
        for (int i=0; i<5; i++) {
            new Thread(saleShop::goShop,"编号:"+(i+1)).start();
        }
    }
}

某一次结果:

编号:1:排队中
编号:3:排队中
编号:2:排队中
编号:1:买东西
编号:2:买东西
编号:3:买东西
编号:4:排队中
编号:4:买东西
编号:5:排队中
编号:5:买东西

Process finished with exit code 0

总结:排队顺序:1,3,2,4,5 买东西顺序:1,2,3,4,5。第三个人是不是插队了

2.ReentrantLock是如何实现实现公平锁和非公平锁的呢?是基于AbstractQueuedSynchronizer(抽象队列同步器,简称AQS)什么是AQS,下一篇我们继续聊

### Java ReentrantLock 公平公平实现原理 #### 公平的特性 在Java并发包`java.util.concurrent.locks.ReentrantLock`中,当创建实例时可以指定是否采用公平策略。对于公平而言,在多个线程竞争同一把的情况下,会按照请求的时间顺序来决定哪个线程获得资源[^1]。 ```java // 创建一个公平 ReentrantLock fairLock = new ReentrantLock(true); ``` #### 公平的行为模式 而公平则不会遵循严格的等待队列规则,允许插队行为发生。这意味着即使有其他线程正在排队等候这把,当前尝试获取该的新线程仍有机会立即占有它而无需严格遵照先入先出的原则[^2]。 ```java // 创建一个公平,默认是公平的 ReentrantLock unfairLock = new ReentrantLock(); ``` #### 内部机制解析 无论是公平还是公平版本的`ReentrantLock`都依赖于AQS(AbstractQueuedSynchronizer),即抽象队列同步器来进行底层控制逻辑的设计。具体来说: - **状态管理**:通过整型变量表示持有者对该拥有的次数; - **FIFO队列维护**:用于记录那些未能成功取得独占权因而被迫进入休眠态直到被唤醒为止的所有候选线程; - 对于公平,每次都会检查队首是否有待处理的任务;而对于公平,则可能直接尝试去抢夺而不必理会已有多少个前驱节点存在[^3]。 #### AQS源码片段展示如何区分两种类型的定操作 下面给出简化版的伪代码用来说明两者之间的差异之处: ```java final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 获取当前状态值 if (c == 0) { // 尝试设置新状态并返回结果, 这里体现了公平性可以直接抢占 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { // 如果已经是拥有者的再次加情况 int nextc = c + acquires; setState(nextc); return true; } return false; } protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (isHeldExclusively()) { // 已经持有的情况下增加计数即可 return true; } if (c == 0 && !hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } return false; } ``` 上述两个方法分别代表了公平公平条件下试图获取的过程。其中`nonfairTryAcquire()`函数总是优先考虑自己能否立刻得到,而不管前面还有没有人排队;相反地,`tryAcquire()`会在调用之前先判断是否存在更早到来的竞争者(`!hasQueuedPredecessors()`)再做决策[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值