如何使用Semaphore?

187 阅读6分钟

Semaphore的介绍

按我的理解是 Semaphore的出现是为了解决在多线程的情况下,并发量过多而导致系统缓慢或崩溃的问题。

它可以限制在同一时刻,能访问方法的线程数

Semaphore 方法

Semaphore的构造器

Semaphore(int permits) 
Semaphore(int permits, boolean fair)

第一个构造器是设置许可证的个数,即同时能允许多少个线程执行方法

第二个构造器有两个参数,第一个参数跟第一个方法一样,第二个方法是设置是否公平策略,设置是否公平。如果是 true,则是公平策略;如果是 false,则是非公平策略

注:许可证的个数是构造方法传入的时候定义的


获得许可证

void acquire() throws InterruptedException
void acquireUninterruptibly()

void acquire(int permits) throws InterruptedException
void acquireUninterruptibly(int permits)

boolean tryAcquire()

acquire() 是获取许可证,如果获得许可证,则继续执行任务,如果没获得许可证,则进入等待,等待其它队列释放许可证。

示例代码

public class SemaphoreDemo {

    static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {

        Thread thread = new Thread(new Task());
        Thread thread1 = new Thread(new Task());
        Thread thread2 = new Thread(new Task());
        thread.start();
        thread1.start();
        try {
            Thread.sleep(1000); //休眠一秒是为了让上面的线程拿到许可证
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
        thread2.interrupt(); //打断被加入等待队列的线程
    }

    static class Task implements Runnable {

        @Override
        public void run() {

            try {
                semaphore.acquire(); //获取许可证,获取了方可进入
                System.out.println(Thread.currentThread().getName() + "拿到许可证");
                Thread.sleep(50000);  //让线程睡个5分钟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "线程直接越过获取acquire的步骤了");

        }
    }
}

在这里插入图片描述 从打印的情况来看,有两个线程拿到许可证并进入睡眠,这时候第三个线程进来后,拿不到许可证,则进入等待队列,等待其它线程释放许可证,但在 main() 方法中,我们最后调用了 thread2.interrupted() 方法,打断了进入等待的那个线程,打断后抛出了 InterruptedException异常,然后跳过了获取 许可证的步骤,执行了 run()方法中最后的一个打印语句,所以可以看出 acquire() 是有响应中断的能力

public class SemaphoreDemo {

    static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {

        Thread thread = new Thread(new Task());
        Thread thread1 = new Thread(new Task());
        Thread thread2 = new Thread(new Task());
        thread.start();
        thread1.start();
        try {
            Thread.sleep(1000); //休眠一秒是为了让上面的线程拿到许可证
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
        thread2.interrupt(); //打断被加入等待队列的线程
    }

    static class Task implements Runnable {

        @Override
        public void run() {

            try {
                semaphore.acquireUninterruptibly(); //获取许可证,获取了方可进入
                System.out.println(Thread.currentThread().getName() + "拿到许可证");
                Thread.sleep(5000);  //让线程睡个5秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "线程苏醒并继续执行");

        }
    }
}

在这里插入图片描述 在这里我们修改了三处地方,一处是获取许可证的那一行代码,从 acquire() 改成了 acquireUninterruptibly();第二处是 run()的最后一句打印语句;第三处是把睡眠的时间从5分钟改成了5秒;从最后执行的情况来看,两个拿到许可证的线程执行苏醒后,执行完run()方法中的代码,但为什么程序还没有停止呢?原因是这样的,因为我没调用 Semaphore类的 release()方法,只有调用了这个方法,线程才会释放许可证,让等待的线程去获取。正因为没有释放,所以第三个线程一直在等待其它线程释放许可证,并拿到许可证去执行,所以程序不会停止,至于 main()方法中的 thread2.interrupted() 为什么不会打断等待的线程呢?因为这个获取许可证的方法,没有能力响应线程中断,所以这个 thread2.interrupted() 方法调用无效。

acquire() 和 acquireUninterrupted()方法两者的区别就在于,acquire() 是可以响应中断的,当线程在获取许可证的时候,如果被中断(acquire() 被我们用 try围住了),那么它会跳过获取许可证,而继续执行下去;acquireUninterruptibly() 方法是不会响应中断的,所以线程在被中断的时候,并不会感知到,也不会抛出异常。

而带有参数的方法,在参数中设置几个,代表了每次同时拿的许可证的个数,只有拿到规定许可证的个数,才能继续运行下去

tryAcquire()方法,是尝试获取许可证,如果有就获取许可证,如果没有就去做别的事情了,也可以继续运行下去

public class SemaphoreDemo {

    static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {

        Thread thread = new Thread(new Task());
        Thread thread1 = new Thread(new Task());
        Thread thread2 = new Thread(new Task());
        thread.start();
        thread1.start();
        thread2.start();

    }

    static class Task implements Runnable {

        @Override
        public void run() {

            try {
                semaphore.tryAcquire(); //获取许可证,获取了方可进入
                System.out.println(Thread.currentThread().getName() + "拿到许可证");
                Thread.sleep(5000);  //让线程睡个5秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "线程苏醒并继续执行");
        }
    }
}

在这里插入图片描述 在run()方法的后面并没有释放许可证,而调用 tryAcquire() 方法却不管这个继续执行下去,由此可以看到,三个线程都打印出了内容(在这里,不敢保证这个方法的解释是正确的,保留下个人见解 @TODO)

注:每次调用 acquire()许可证就减1,直到减为0为止,减到0,后面的线程就陷入等待,等线程执行完毕后,释放许可证,即许可证加1,后面的线程就可以继续获取这个许可证了。

而 acquire(int permits)是里面的参数多少,就代表着一次要获取许可证的个数,获取一次,减 permits 个,释放也就是释放 permits个,但剩下的许可证个数不足 permits 的时候,线程进入等待,等其它线程释放许可证后再去获取

注:设置一次获取许可证的个数 ≤ 构造器设置的许可证个数

释放许可证

void release()
void release(int permits)

release():方法是释放一个许可证 release(int permits):是释放传入参数个数的许可证

示例代码

public class SemaphoreDemo {

    static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {

        Thread thread = new Thread(new Task());
        Thread thread1 = new Thread(new Task());
        Thread thread2 = new Thread(new Task());
        thread.start();
        thread1.start();
        thread2.start();

    }

    static class Task implements Runnable {

        @Override
        public void run() {

            try {
                semaphore.acquireUninterruptibly(); //获取许可证,获取了方可进入
                System.out.println(Thread.currentThread().getName() + "拿到许可证");
                Thread.sleep(5000);  //让线程睡个5分钟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "线程苏醒并继续执行");
            semaphore.release(); //释放许可证
        }
    }
}

在这里插入图片描述 run() 方法的最后一个方法是调用了 release() 方法,从打印结果来看,当前面的线程苏醒后并释放了 许可证,前面新的线程就可以拿到许可证,然后继续运行了。

查询许可证的个数

int availablePermits() 

返回的是处于没人获取状态的许可证

实例代码

public class SemaphoreDemo {

    static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(new Task());
        Thread thread1 = new Thread(new Task());
        Thread thread2 = new Thread(new Task());
        thread.start();
        Thread.sleep(500);
        thread1.start();
        Thread.sleep(500);
        thread2.start();

    }

    static class Task implements Runnable {

        @Override
        public void run() {

            try {
                System.out.println("许可证的个数:" + semaphore.availablePermits());
                semaphore.acquire(); //获取许可证,获取了方可进入
                System.out.println(Thread.currentThread().getName() + "拿到许可证");
                System.out.println("-----------------------------------------");
                Thread.sleep(5000);  //让线程睡个5秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "线程苏醒并继续执行");
            semaphore.release();
        }
    }
}

在这里插入图片描述 从结果来看,当第一个线程进入 run()还没拿到许可证的时候,打印的是2个许可证;但第二个线程进入 run()方法的时候,打印的是1个许可证,因为第一个线程已经拿走了一个许可证。当第三个线程进入 run()方法的时候,打印的是0个许可证,因为许可证就2张,还都被前面的线程拿走。由此可见,availablePermits() 方法打印的是剩余许可证的数量。


欢迎大家关注下个人的「公众号」:独醉贪欢