Semaphore是一个计数信号量,必须由获取它的线程释放。SemaphoreSynchronized的加强版,常用于限制可以访问某些资源的线程数量,例如通过Semaphore限流。Semaphore 只有3个操作:初始化、增加、减少

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class StudySemaphore {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();

        //信号量,只允许 3个线程同时访问
        Semaphore semaphore = new Semaphore(3);

        for (int i=0;i<10;i++){
            final long num = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取许可
                        semaphore.acquire();
                        //执行
                        System.out.println("Accessing: " + num);
                        Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
                        //释放
                        semaphore.release();
                        System.out.println("Release..." + num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        executorService.shutdown();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

方法acquire( int permits )参数作用: 动态添加permits许可数量。可以这么理解,new Semaphore(6)表示初始化了 6个通路,semaphore.acquire(2)表示每次线程进入将会占用2个通路,semaphore.release(2)运行时表示归还2个通路。没有通路,则线程就无法进入代码块。上面的代码中,semaphore.acquire()+semaphore.release()在运行的时候,其实和semaphore.acquire(1)+semaphore.release(1)效果是一样的。

如果acquire的数量大于release的数量,则通路迟早会被使用完,如果线程比较多,得不到后续运行,出现线程堆积内存,最终java进程崩掉;如果acquire的数量小于release的数量,就会出现并发执行的线程越来越多(换句话说,处理越来越快),最终也有可能出现问题。

acquire 的不可中断实现: semaphore.acquire()semaphore.acquire(int permits)是会抛出异常InterruptedException的,如果在acquirerelease之间的代码是一个比较慢和复制的运算,如内存占用过多,或者栈深度很深等,jvm会中断这块代码。使用 acquireUninterruptibly()替换acquire()、使用acquireUninterruptibly(int permits)替换acquire(int permits) ,才能不让jvm中断代码执行。acquireUninterruptibly不会抛出InterruptedException ,一个代码块一时执行不完,还会继续等待执行。

个人觉得,不要随便使用acquireUninterruptibly ,因为jvm中断执行,是自身的一种自我保护机制,保证java进程的正常,除了特殊情况必须用acquireUninterruptibly外,都应该使用acquire,将release放到finally块中。

其他一些常有工具方法:
【1】availablePermits()表示返回Semaphore对象中的当前可用许可数,此方法通常用于调试,因为许可数量(通路)可能是实时在改变的。
【2】drainPermits()方法可获取并返回立即可用的所有许可(通路)个数,并将可用许可置为0。
【3】getQueueLength()获取等待许可的线程个数。
【4】getQueueLength()hasQueuedThreads()都是在判断当前有没有等待许可的线程信息时使用。

线程公平性,上面用的Semaphore构造方法是Semaphore semaphore = new Semaphore(int permits)。其实,还有一个构造方法:Semaphore semaphore = new Semaphore(int permits , boolean isFair)isFair的意思是否公平,获得锁的顺序与线程启动顺序有关,就是公平,先启动的线程,先获得锁。isFair不能100% 保证公平,只能是大概率公平。isFairtrue,则表示公平,先启动的线程先获得锁。

tryAcquire(int permits , long timeout , TimeUint unit) //在指定时间 timeout内尝试获取 permits个通路。
1
(adsbygoogle = window.adsbygoogle || []).push({});