了解信号量Semaphore和线程池的差异

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/mryang125/article/details/81490783

一、首先要明白Semaphore和线程池各自是干什么?

信号量Semaphore是一个并发工具类,用来控制可同时并发的线程数,其内部维护了一组虚拟许可,通过构造器指定许可的数量,每次线程执行操作时先通过acquire方法获得许可,执行完毕再通过release方法释放许可。如果无可用许可,那么acquire方法将一直阻塞,直到其它线程释放许可。

线程池用来控制实际工作的线程数量,通过线程复用的方式来减小内存开销。线程池可同时工作的线程数量是一定的,超过该数量的线程需进入线程队列等待,直到有可用的工作线程来执行任务。

使用Seamphore,你创建了多少线程,实际就会有多少线程进行执行,只是可同时执行的线程数量会受到限制。但使用线程池,你创建的线程只是作为任务提交给线程池执行,实际工作的线程由线程池创建,并且实际工作的线程数量由线程池自己管理。

简单来说,线程池实际工作的线程是work线程,不是你自己创建的,是由线程池创建的,并由线程池自动控制实际并发的work线程数量。而Seamphore相当于一个信号灯,作用是对线程做限流,Seamphore可以对你自己创建的的线程做限流(也可以对线程池的work线程做限流),Seamphore的限流必须通过手动acquire和release来实现。

区别就是两点:

1、实际工作的线程是谁创建的?

使用线程池,实际工作线程由线程池创建;使用Seamphore,实际工作的线程由你自己创建。

2、限流是否自动实现?

线程池自动,Seamphore手动。

以下通过具体的案例来说明二者具体的区别。

1)使用Semaphore:

  public static void testSeamphore() {
    Semaphore semaphore = new Semaphore(2);
    for (int i = 0; i < 5; i++) {
      Thread thread = new Thread() {
        public void run() {
          try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " start running **********************");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " stop running  ----------------------");
            semaphore.release();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      };
      thread.setName("my thread " + i);
      thread.start();
    }
  }

我们创建了5个线程,信号量为2,打印如下:

my thread 0 start running **********************
my thread 1 start running **********************
my thread 0 stop running  ----------------------
my thread 1 stop running  ----------------------
my thread 2 start running **********************
my thread 3 start running **********************
my thread 3 stop running  ----------------------
my thread 2 stop running  ----------------------
my thread 4 start running **********************
my thread 4 stop running  ----------------------

通过控制台容易观察可知:

1、每次最多会打印2条***的记录,是因为Seamphore信号量为2,故而控制了实际并发的线程数量为2。

2、5条线程的名字都是以my thread 开头,说明实际工作的线程是我们自己创建的。

2)使用线程池:

  public static void testPool() {
    ExecutorService executorService = new ThreadPoolExecutor(2, 5,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>());

    for (int i = 0; i < 5; i++) {
      Thread thread = new Thread() {
        public void run() {
          try {
            System.out.println(Thread.currentThread().getName() + " start running **********************");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " stop running  ----------------------");
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      };
      thread.setName("my thread " + i);
      executorService.submit(thread);
    }
    executorService.shutdown();
  }

我们创建了一个核心容量为2,总容量为5的线程池,并行执行5个线程任务。打印如下:

pool-1-thread-1 start running **********************
pool-1-thread-2 start running **********************
pool-1-thread-2 stop running  ----------------------
pool-1-thread-1 stop running  ----------------------
pool-1-thread-1 start running **********************
pool-1-thread-2 start running **********************
pool-1-thread-1 stop running  ----------------------
pool-1-thread-2 stop running  ----------------------
pool-1-thread-1 start running **********************
pool-1-thread-1 stop running  ----------------------

通过控制台容易观察容易知道:

1、每次最多两条线程执行,这是由核心线程容量决定的,多余的线程任务放到阻塞队列等待。

2、实际工作线程不是我们自己创建的,是线程池提供的。它们是pool-1-thread-1和pool-1-thread-2。

二、Semaphore作为互斥锁的体现

Semaphore实现互斥锁的方式是使用初始值为1的Semaphore对象,这样每条线程获取许可后必须释放许可,其它线程才能获取许可,当前拥有许可的线程就拥有了互斥锁。

以下是具体案例:

    public static void testMutex() {
        Semaphore semaphore = new Semaphore(1);
        for (int i = 0; i < 5; i++) {
            new Thread() {
                public void run() {
                    try {
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "已获得许可");
                        Thread.sleep(2000);
                        System.out.println(Thread.currentThread().getName() + "已释放许可");
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }

控制台打印如下:

Thread-0已获得许可
Thread-0已释放许可
Thread-1已获得许可
Thread-1已释放许可
Thread-2已获得许可
Thread-2已释放许可
Thread-3已获得许可
Thread-3已释放许可
Thread-4已获得许可
Thread-4已释放许可

可以看出,任何一个线程在释放许可之前,其它线程都拿不到许可。这样当前线程必须执行完毕,其它线程才可执行。这样就实现了互斥。

三、Semaphore先release后acquire

Seamphore有一种特殊的使用场景,即先释放许可,后申请许可,此时会额外增加一个许可。

实际编程中要额外小心,如下的实例,通过new Semaphore(0)创建的信号量,默认许可数是0,如果先调用release,会增加一个许可,再次acquire便可以获取新增的许可。

    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(0);
        System.out.println(semaphore.availablePermits());
        semaphore.release();
        System.out.println(semaphore.availablePermits());
        semaphore.acquire(); //阻塞
        System.out.println(semaphore.availablePermits());
    }

所以上面的代码实际上不会发生阻塞,而是直接输出0 1 0。本例中如果将release和acquire调换位置,则一定会发生阻塞。

0
1
0

 

展开阅读全文

没有更多推荐了,返回首页