9.Semaphore(非公平共享锁)

327 阅读4分钟

Semaphore(非公平共享锁)

1.Semaphore是什么

Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。可以把它简单的理解成我们停车场入口立着的那个显示屏,每有一辆车进入停车场显示屏就会显示剩余车位减1,每有一辆车从停车场出去,显示屏上显示的剩余车辆就会加1,当显示屏上的剩余车位为0时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止。

2.使用场景:限流

主要用于那些资源有明确访问数量限制的场景,常用于限流 。

比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。

比如:停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。

3.Semaphore常用方法说明

/***
acquire()  
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
​
acquire(int permits)  
获取permits个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
    
acquireUninterruptibly() 
获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
    
tryAcquire()
尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
​
tryAcquire(long timeout, TimeUnit unit)
尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
​
release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
​
hasQueuedThreads()
等待队列里是否还存在等待线程。
​
getQueueLength()
获取等待队列里阻塞的线程数。
​
drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。
​
availablePermits()
返回可用的令牌数量。
​
semaphore.acquire()可以在一个线程多次获取
semaphore.release()在一个线程多次获取后必须多次释放
***/

4.停车场提示牌

每个停车场入口都有一个提示牌,上面显示着停车场的剩余车位还有多少,当剩余车位为0时,不允许车辆进入停车场,直到停车场里面有车离开停车场,这时提示牌上会显示新的剩余车位数。

业务场景 :

1.停车场容纳总停车量10。

2.当一辆车进入停车场后,显示牌的剩余车位数相应的减1.

3.每有一辆车驶出停车场后,显示牌的剩余车位数相应的加1。

4.停车场剩余车位不足时,车辆只能在外面等待。

package org.apache.rocketmq.example.thread;
​
import java.util.Random;
import java.util.concurrent.Semaphore;
​
public class TestCar3 {
    //停车场同时容纳的车辆10
    private static Semaphore semaphore = new Semaphore(10);
    
    public static void main(String[] args) {
        //模拟100辆车进入停车场
        for (int i = 0; i <100 ; i++) {
            Thread thread=new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("===="+Thread.currentThread().getName()+"来到停车场");
                        if(semaphore.availablePermits()==0){
                            System.out.println("车位不足,请耐心等待");
                        }
                        // semaphore.acquire()可以在一个线程多次获取
                        //获取令牌尝试进入停车场
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName()+"成功进入停车场"); 
                        //模拟车辆在停车场停留的时间
                        Thread.sleep(new Random().nextInt(10000));
                        System.out.println(Thread.currentThread().getName()+"驶出停车场");
                        
                        // semaphore.release()在一个线程多次获取后必须多次释放
                        // 释放令牌,腾出停车场车位
                        semaphore.release();
                        
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }},i+"号车");
            thread.start();
        }
    }
}
​

5.加锁解锁流程

Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就像是获得了停车位许可,然后停车场显示空余车位减一 刚开始,permits(state)为 3,这时 5 个线程来获取资源

image.png

image.png

image.png

image.png

6.思考

问题1:permits是0,为什么还要唤醒Thread-3?

问题2:为什么Thread-0被设置为头结点后thread是null?