JUC之AQS之Semaphore(信号量)

Semaphore为多线程协作提供了强大的控制方法,可以看成是对锁的一种扩展。无论是内部锁synchornized还是重入锁ReetrantLock,一次都允许一个线程访问一个资源,Semaphore可以限制多个线程同时访问某个资源的个数。比如限制数据库访问的连接数。

Semaphore提供了两个构造方法(第一个参数是许可证的数量,当许可证数量为1就和单线程很相似了;后面的fair可以指定是否公平,默认是不公平,公平模式就是调用acquire的顺序就是获取许可证的顺序,遵循FIFO;而非公平模式是抢占式的,也就是有可能一个新的获取线程恰好在一个许可证释放时得到了这个许可证,而前面还有等待的线程。):

主要有以下几个方法:

  • void acquire()
  • void acquireUninterruptibly()
  • boolean tryAcquire()   有多个重载方法
  • boolean tryAcquire(long timeout,TimeUnit unit)   有多个重载方法
  • void release()
  • int drainPermits()    获取剩余许可的数量,可获取并返回立即可用的所有许可个数,并且将可用许可置0
  • availablePermits()   返回此Semaphore对象中当前可用的许可数,许可的数量有可能实时在改变,并不是固定的数量
  • reducePermits()      减小许可数量,该方法可以用于用于当资源用完不能再用时,这时就可以减小许可证

acquire()方法尝试获得一个准入的许可。如果无法获得,则线程会等待 ,直到有一个线程释放一个许可或者当前线程被中断。acquireUninterruptibly()和acquire()方法类似,但是不响应中断。tryAcquire()尝试获得一个许可,如果获得成功会返回true,失败则会返回false,它不会继续等待,立即返回。release()用于再线程访问资源结束后,释放一个许可,让其他等待许可的线程可以继续进行资源访问。

下面是一段简单的示例:

package dgb.test.concurrent;

import java.time.LocalDateTime;
import java.util.concurrent.*;

/**
 * @author Dongguabai
 * @date 2018/9/4 9:22
 */
public class SemaphoreTest implements Runnable{

    private final Semaphore semaphore = new Semaphore(5);

    @Override
    public void run() {
        try {
            semaphore.acquire();
            System.out.println(LocalDateTime.now() + "---线程【" + Thread.currentThread().getName() + "】正在执行耗时的操作---");
            TimeUnit.SECONDS.sleep(1);
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SemaphoreTest semaphoreTest = new SemaphoreTest();
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 20; i++) {
            executorService.submit(semaphoreTest);
        }
    }
}

运行结果:

JUC之AQS之Semaphore(信号量)_第1张图片

可以很明显的看出,是每秒5个线程在执行。

程序会限制执行“耗时操作”代码的线程数。这里定义了一个有5个许可的信号量,也就是说同时可以有5个线程执行耗时操作”代码。申请信号量使用acquire(),操作执行完毕需要使用release()释放信号量,跟释放锁差不都的意思。如果信号量申请了但是没有释放,那么可以进入临界区的线程数会减少。

再将上面代码中的acquire()改成tryAcquire()执行,也就是说多余访问临界区的线程将会直接被丢弃:

package dgb.test.concurrent;

import java.time.LocalDateTime;
import java.util.concurrent.*;

/**
 * @author Dongguabai
 * @date 2018/9/4 9:22
 */
public class SemaphoreTest implements Runnable{

    private final Semaphore semaphore = new Semaphore(5);

    @Override
    public void run() {
        try {
            if (semaphore.tryAcquire()) {
                System.out.println(LocalDateTime.now() + "---线程【" + Thread.currentThread().getName() + "】正在执行耗时的操作---");
                TimeUnit.SECONDS.sleep(1);
                semaphore.release();
            }else {
                System.out.println(LocalDateTime.now() + "---线程【" + Thread.currentThread().getName() + "】被丢弃");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SemaphoreTest semaphoreTest = new SemaphoreTest();
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 20; i++) {
            executorService.submit(semaphoreTest);
        }
    }
}

执行结果:

JUC之AQS之Semaphore(信号量)_第2张图片

你可能感兴趣的:(JUC之AQS之Semaphore(信号量))