java并发-Semaphore

16 阅读5分钟

当下Java并发编程日益普及,而Semaphore是Java提供的一种功能强大的线程同步工具,可用于控制同时访问系统资源的线程数量。本篇博客将从Semaphore的简介、底层实现和应用场景等角度,深入探讨Semaphore的原理以及其实现和使用方法。

1、Semaphore的简介

Semaphore是一种计数器,用于控制同时访问资源的线程数量。它通过一个计数器来表达可用资源的数量,当有新线程需要访问该资源时,会先进行请求,然后进行P(proberen)操作,如果此时计数器的值为0,则线程就会被阻塞并进入等待队列中,等到其他线程释放资源后才重新获得控制权。而当线程释放资源时,则会进行V(verhogen)操作,将计数器的值加1,以通知其他线程可以继续请求访问资源。

Semaphore的构造方法有两个参数,分别表示初始的资源数量和是否为公平锁。如果将公平锁设置为true,则多个线程并发访问Semaphore时,会按照它们的请求顺序进行处理,即先请求的线程也会先获得资源。

Semaphore是一个典型的Java并发库类,位于java.util.concurrent包中。

2、Semaphore的底层实现

在Semaphore的底层实现中,它是基于AQS(AbstractQueuedSynchronizer)的同步器实现的。AQS是一个为JDK提供的一个框架,用于实现同步器,比如锁、Semaphore等,是基于队列的锁和同步器的实现框架。

Semaphore的底层实现主要基于两个方法:

  • acquire():申请许可证,实际上就是P操作。
  • release():释放许可证,实际上就是V操作。

在Semaphore的实现中,如果许可证数量大于0,则线程可以直接获取许可证资源并执行任务。如果许可证数量小于等于0,则线程会被阻塞,直到其他线程释放许可证资源,则当前线程才能重新获取许可证并执行任务。需要注意的是,一旦线程获取到许可证资源,则许可证数量就会减1。同理,每次释放许可证资源,许可证数量就会加1。

3、Semaphore的应用场景

Semaphore的应用场景较为广泛,主要用于控制并发数量,防止线程阻塞和死锁等问题。下面列举一些Semaphore的应用场景和示例代码:

场景1:限流器

Semaphore可用于实现限流器,即限制同时访问某个资源的线程数量。比如,我们可以使用Semaphore控制某个接口的并发数量,防止服务被过度访问而崩溃。下面是示例代码:

public class RateLimit {  
  private Semaphore semaphore;

  public RateLimit(int permits) {  
    this.semaphore = new Semaphore(permits);  
  }

  public void acquire() {  
    semaphore.acquire();  
  }

  public void release() {  
    semaphore.release();  
  }  
}  

场景2:数据库连接池

Semaphore可用于实现数据库连接池,即限制同时连接数据库的线程数量。比如,我们可以使用Semaphore来限制连接数,防止数据库过度加载而崩溃。下面是示例代码:

public class DBConnectionPool {  
  private final Semaphore semaphore;  
  private final List<Connection> pool;

  public DBConnectionPool(int permits, List<Connection> pool) {  
    this.semaphore = new Semaphore(permits, true);  
    this.pool = pool;  
  }

  public Connection acquire() throws InterruptedException {  
    semaphore.acquire();  
    return pool.remove(0);  
  }

  public void release(Connection conn) {  
    pool.add(conn);  
    semaphore.release();  
  }  
}  

场景3:线程池

Semaphore可用于实现线程池,即限制同时执行任务的线程数量。比如,我们可以使用Semaphore来控制线程池的大小,防止同时执行太多的任务而导致系统崩溃。下面是示例代码:

public class MyThreadPool {  
  private final Semaphore semaphore;  
  private final ExecutorService executor;

  public MyThreadPool(int permits, ExecutorService executor) {  
    this.semaphore = new Semaphore(permits);  
    this.executor = executor;  
  }

  public void execute(Runnable task) throws InterruptedException {  
    semaphore.acquire();  
    executor.execute(() -> {  
      try {  
        task.run();  
      } finally {  
        semaphore.release();  
      }  
    });  
  }  
}  

4、Semaphore的示例代码

下面给出一个基于Semaphore实现的生产者-消费者模型的示例代码,具体实现可以参考如下:

import java.util.LinkedList;  
import java.util.Queue;  
import java.util.concurrent.Semaphore;

class QueueBuffer<T> {  
  Queue<T> buffer = new LinkedList<>();  
  Semaphore producerSemaphore = new Semaphore(1);  
  Semaphore consumerSemaphore = new Semaphore(0);  
  int maxBufferSize = 10;

  public void put(T obj) throws InterruptedException {  
    producerSemaphore.acquire();  
    try {  
      while (buffer.size() == maxBufferSize) {  
        consumerSemaphore.release();  
        producerSemaphore.acquire();  
      }  
      buffer.add(obj);  
      System.out.println(Thread.currentThread().getName() + " put: " + obj);  
      consumerSemaphore.release();  
    } finally {  
      producerSemaphore.release();  
    }  
  }

  public T get() throws InterruptedException {  
    consumerSemaphore.acquire();  
    try {  
      while (buffer.isEmpty()) {  
        producerSemaphore.release();  
        consumerSemaphore.acquire();  
      }  
      T obj = buffer.poll();  
      System.out.println(Thread.currentThread().getName() + " get: " + obj);  
      producerSemaphore.release();  
      return obj;  
    } finally {  
      consumerSemaphore.release();  
    }  
  }  
}

public class ProducerConsumer {  
  public static void main(String[] args) throws InterruptedException {  
    QueueBuffer<Integer> buffer = new QueueBuffer<>();  
    Thread producer1 = new Thread(() -> {  
      try {  
        for (int i = 1; i <= 10; i++) {  
          buffer.put(i);  
          Thread.sleep(1000);  
        }  
      } catch (InterruptedException e) {  
        e.printStackTrace();  
      }  
    }, "Producer1");

    Thread producer2 = new Thread(() -> {  
      try {  
        for (int i = 11; i <= 20; i++) {  
          buffer.put(i);  
          Thread.sleep(1000);  
        }  
      } catch (InterruptedException e) {  
        e.printStackTrace();  
      }  
    }, "Producer2");

    Thread consumer1 = new Thread(() -> {  
      try {  
        for (int i = 1; i <= 5; i++) {  
          buffer.get();  
          Thread.sleep(2000);  
        }  
      } catch (InterruptedException e) {  
        e.printStackTrace();  
      }  
    }, "Consumer1");

    Thread consumer2 = new Thread(() -> {  
      try {  
        for (int i = 6; i <= 10; i++) {  
          buffer.get();  
          Thread.sleep(2000);  
        }  
      } catch (InterruptedException e) {  
        e.printStackTrace();  
      }  
    }, "Consumer2");

    producer1.start();  
    producer2.start();  
    consumer1.start();  
    consumer2.start();

    producer1.join();  
    producer2.join();  
    consumer1.join();  
    consumer2.join();  
  }  
}  

以上代码基于Semaphore实现了一个生产者-消费者模型,并且内置了两个生产者和两个消费者线程,其中生产者会生产1-20的数字,并将其放入缓冲区中,而消费者会从缓冲区中取出数据并进行处理(这里采用简单的输出操作)。而Semaphore则用于控制两个生产者之间的并发数量和两个消费者之间的并发数量,以确保线程安全性和程序的正确性。

结语

Semaphore是Java中一个非常实用的线程同步工具,可以帮助开发者控制并发访问数量、防止线程阻塞和死锁等问题,在多种场景下都有着广泛的应用。本文中介绍了Semaphore的简介、底层实现和应用场景,同时演示了Semaphore在生产消费模型中的实现方式,相信读者可以通过本文进一步熟悉Semaphore的使用方法,从而提高对Java并发编程的理解和实践能力。