Java并发编程之Semaphore

简介

Semaphore是计数信号量,管理一系列许可证。
线程通过acquire方法获取许可证,成功则许可证总数减一并执行任务,反之阻塞等待;
线程通过release方法释放许可证,许可证总数加一。

公平与非公平模式

	// 默认非公平模式
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

	// 可设置公平或非公平
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;

        NonfairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }

    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
        	// 总许可证数
            int available = getState();
            // 计算剩余许可证数 = 总许可证数 - 被当前线程拿走的许可证数
            int remaining = available - acquires;
            // 剩余的必须大于零,然后更新总许可证数为剩余许可证数
            if (remaining < 0 || compareAndSetState(available, remaining))
                return remaining;
        }
    }
    static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;

        FairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            for (;;) {
            	// 查看是否有等待队列,有排队
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

非公平与公平获取许可证的不同在于:
非公平获取许可证时,不会查看等待队列,无法保证线程获取许可证的顺序,插队是允许;
公平模式则能保证获取许可的顺序为线程调用acquire方法的顺序–FIFO(先进先出)

示例1 单许可证

    public static void demo01() throws InterruptedException {
        Semaphore semaphore = new Semaphore(1);
        Thread t1 = new Thread(() -> method01(semaphore), "t1");
        Thread t2 = new Thread(() -> method01(semaphore), "t2");
        Thread t3 = new Thread(() -> method01(semaphore), "t3");

        t1.start();
        t2.start();
        t3.start();
    }

    private static void method01(Semaphore semaphore) {
        String thName = Thread.currentThread().getName();
        try {
            long start = System.currentTimeMillis();
            Thread.sleep((long) (Math.random() * 1000));
            System.out.println("线程: " + thName + "执行acquire方法耗时:" + (System.currentTimeMillis() - start));
            Thread.sleep(1000);
            semaphore.acquire();
            System.out.println("线程: " + thName + "获得许可");

            long start2 = System.currentTimeMillis();
            Thread.sleep((long) (Math.random() * 3000));
            System.out.println("线程: " + thName + "任务耗时:" + (System.currentTimeMillis() - start2));

            Thread.sleep(1000);
            System.err.println("线程: " + thName + "释放许可");
            semaphore.release();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

总许可证数为1,同一时刻只能有一个线程持有,未持有的线程阻塞等待
Java并发编程之Semaphore_第1张图片

示例2 多许可证,单持有

    public static void demo02() throws InterruptedException {
        Semaphore semaphore = new Semaphore(2);
        Thread t1 = new Thread(() -> method01(semaphore), "t1");
        Thread t2 = new Thread(() -> method01(semaphore), "t2");
        Thread t3 = new Thread(() -> method01(semaphore), "t3");
        Thread t4 = new Thread(() -> method01(semaphore), "t4");
        Thread t5 = new Thread(() -> method01(semaphore), "t5");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

总许可证数大于1,同一时刻可以有多个线程持有
Java并发编程之Semaphore_第2张图片

示例3 多许可证,多持有

    public static void demo02() throws InterruptedException {
        Semaphore semaphore = new Semaphore(2);
        Thread t1 = new Thread(() -> method02(semaphore), "t1");
        Thread t2 = new Thread(() -> method02(semaphore), "t2");
        Thread t3 = new Thread(() -> method02(semaphore), "t3");
        Thread t4 = new Thread(() -> method02(semaphore), "t4");
        Thread t5 = new Thread(() -> method02(semaphore), "t5");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

    private static void method02(Semaphore semaphore) {
        String thName = Thread.currentThread().getName();
        try {
            long start = System.currentTimeMillis();
            Thread.sleep((long) (Math.random() * 1000));
            System.out.println("线程: " + thName + "执行acquire方法耗时:" + (System.currentTimeMillis() - start));
            Thread.sleep(1000);
            semaphore.acquire(2);
            System.out.println("线程: " + thName + "获得许可");

            long start2 = System.currentTimeMillis();
            Thread.sleep((long) (Math.random() * 3000));
            System.out.println("线程: " + thName + "任务耗时:" + (System.currentTimeMillis() - start2));

            Thread.sleep(1000);
            System.err.println("线程: " + thName + "释放许可");
            semaphore.release(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

线程需要持有多个许可证才能执行任务,这里跟单许可证单持有是一样
Java并发编程之Semaphore_第3张图片

示例4 超时等待

acquire():获取许可证,失败则阻塞等待;
tryAcquire():尝试获取许可证,失败后不会阻塞,继续执行
tryAcquire(long timeout, TimeUnit unit):在超时时间内阻塞等待获取许可证,超时后唤醒继续执行;

    public static void demo03() throws InterruptedException {
        Semaphore semaphore = new Semaphore(1);
        Thread t1 = new Thread(() -> method03_1(semaphore), "t1");
        Thread t2 = new Thread(() -> method03_1(semaphore), "t2");
        Thread t3 = new Thread(() -> method03_1(semaphore), "t3");

        t1.start();
        t2.start();
        t3.start();
    }

    private static void method03_1(Semaphore semaphore) {
        String thName = Thread.currentThread().getName();
        try {
            long start = System.currentTimeMillis();
            Thread.sleep((long) (Math.random() * 1000));
            System.out.println("线程: " + thName + "执行acquire方法耗时:" + (System.currentTimeMillis() - start));
            Thread.sleep(1000);
            while (true) {
                if (semaphore.tryAcquire()) {
                    System.out.println("线程: " + thName + "获得许可");

                    long start2 = System.currentTimeMillis();
                    Thread.sleep((long) (Math.random() * 3000));
                    System.out.println("线程: " + thName + "任务耗时:" + (System.currentTimeMillis() - start2));

                    Thread.sleep(1000);
                    System.err.println("线程: " + thName + "释放许可");
                    semaphore.release();
                    break;
                } else {
                    System.out.println("线程: " + thName + "获取许可失败");
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Java并发编程之Semaphore_第4张图片

private static void method03_2(Semaphore semaphore) {
        String thName = Thread.currentThread().getName();
        try {
            long start = System.currentTimeMillis();
            Thread.sleep((long) (Math.random() * 1000));
            System.out.println("线程: " + thName + "执行acquire方法耗时:" + (System.currentTimeMillis() - start));
            Thread.sleep(1000);
            if (semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)) {
                System.out.println("线程: " + thName + "获得许可");

                long start2 = System.currentTimeMillis();
                Thread.sleep((long) (Math.random() * 3000));
                System.out.println("线程: " + thName + "任务耗时:" + (System.currentTimeMillis() - start2));

                Thread.sleep(1000);
                System.err.println("线程: " + thName + "释放许可");
                semaphore.release();
            } else {
                System.err.println("线程: " + thName + "超时了");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Java并发编程之Semaphore_第5张图片

特点

持有的许可证,不是必须要当前线程释放,可由其他线程释放;
一个线程可获取释放多个许可证;

你可能感兴趣的:(多线程,并发编程,多线程,并发编程)