一、Semaphore
emaphore 是 Java 并发包 (java.util.concurrent) 中的一个同步工具类,类Semaphore所提供的功能完全就是synchronized关键字的升级版,但它提供的功能更加的强大与方便,主要的作用就是控制线程并发的数量,而这一点,单纯地使用synchronized是做不到的。
emaphore 它用来控制访问某个资源的线程数量。你可以将 Semaphore 想象成一个计数器,它控制着对某个共享资源的访问许可(permits)的数量。当许可用完时,其他线程必须等待,直到有线程释放许可。
emaphore 定义最多允许多少个线程执行acquire()和release()之间的代码。
为什么要限制线程的数量?
—— 如果不限制线程并发的数量,则CPU的资源很快就被耗尽,每个线程执行的任务是相当缓慢,因为CPU要把时间片分配给不同的线程对象,而且上下文切换也要耗时,最终造成系统运行效率大幅降低,所以限制并发线程的数量还是非常有必要的。
Semaphore 的基本用法
• Semaphore(int permits): 创建一个带有给定许可数量的 Semaphore。
• acquire(): 获取一个许可。如果没有许可可用,则阻塞,直到有许可可用。
• release(): 释放一个许可,将其返回给 Semaphore。
1.1 Semaphore 示例:停车场模拟
假设有一个停车场,最多只能容纳 3 辆车。Semaphore 可以用来控制停车场的进入和离开。
import java.util.concurrent.Semaphore;
public class ParkingLot {
private final Semaphore semaphore;
public ParkingLot(int slots) {
// 创建一个 Semaphore,允许的最大并发数是 slots
this.semaphore = new Semaphore(slots);
}
public void parkCar(String car) {
try {
System.out.println(car + " is trying to enter the parking lot.");
// 获取一个许可,可能会阻塞直到有可用的许可
semaphore.acquire();
System.out.println(car + " has parked.");
// 模拟停车一段时间
Thread.sleep((long) (Math.random() * 10000));
System.out.println(car + " is leaving the parking lot.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放许可,表示车已经离开
semaphore.release();
System.out.println(car + " has left the parking lot.");
}
}
public static void main(String[] args) {
ParkingLot parkingLot = new ParkingLot(3); // 停车场有3个车位
Runnable car = () -> {
String carName = Thread.currentThread().getName();
parkingLot.parkCar(carName);
};
// 创建多个线程来模拟不同的车
for (int i = 1; i <= 5; i++) {
new Thread(car, "Car " + i).start();
}
}
}
运行结果示例:
Car 1 is trying to enter the parking lot.
Car 1 has parked.
Car 2 is trying to enter the parking lot.
Car 2 has parked.
Car 3 is trying to enter the parking lot.
Car 3 has parked.
Car 4 is trying to enter the parking lot.
Car 5 is trying to enter the parking lot.
Car 1 is leaving the parking lot.
Car 1 has left the parking lot.
Car 4 has parked.
...
说明
• Semaphore(3):停车场允许 3 辆车同时进入。
• acquire():车想要进入停车场。如果没有空位(许可用完),它将等待。
• release():车离开停车场,释放一个许可。
当所有 3 个许可都被占用时,其他车必须等待,直到有车离开并释放许可为止。这个示例展示了如何使用 Semaphore 控制对共享资源(停车位)的并发访问。
1.2 常用方法
以下是 Semaphore 常用的方法及其功能说明:
1. 构造方法
• Semaphore(int permits): 创建一个许可数为 permits 的 Semaphore,许可数是可以被线程获取的信号量的数量。
• Semaphore(int permits, boolean fair): 创建一个许可数为 permits 的 Semaphore,并指定是否采用公平策略。如果 fair 为 true,则线程将按先来先得的顺序获取许可;如果为 false,则线程的顺序是未指定的(更高效)。
2. 获取许可
• void acquire() throws InterruptedException: 从信号量中获取一个许可。如果没有可用的许可,则线程会被阻塞,直到有许可被释放为止。
• void acquire(int permits) throws InterruptedException: 从信号量中获取指定数量的许可,如果没有足够的许可,线程会被阻塞。
• boolean tryAcquire(): 尝试获取一个许可,如果成功则返回 true,否则返回 false。此方法不会阻塞线程。
• boolean tryAcquire(int permits): 尝试获取指定数量的许可,如果成功则返回 true,否则返回 false。此方法不会阻塞线程。
• boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException: 在给定的超时时间内尝试获取一个许可,如果在超时时间内获取到了许可则返回 true,否则返回 false。
• boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException: 在给定的超时时间内尝试获取指定数量的许可。
3. 释放许可
• void release(): 释放一个许可,将其返回到信号量中。
• void release(int permits): 释放指定数量的许可,将其返回到信号量中。
4. 查询许可
• int availablePermits(): 返回当前可用的许可数。
• int drainPermits(): 获取并返回当前所有可用的许可,并将其从信号量中移除(即将可用许可数设置为0)。
• boolean hasQueuedThreads(): 检查是否有线程正在等待获取许可。
• int getQueueLength(): 返回正在等待获取许可的线程数。
1.3 Semaphore+ReentrantLock+Condition实例
package tools;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ListPool {
private int poolMaxSize = 3;
private int semaphorePermits = 5;
private List<String> list = new ArrayList<String>();
private Semaphore concurrencySemaphore = new Semaphore(semaphorePermits);
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public ListPool() {
super();
for (int i = 0; i < poolMaxSize; i++) {
list.add("高洪岩" + (i + 1));
}
}
public String get() {
String getString = null;
try {
concurrencySemaphore.acquire();
lock.lock();
while (list.size() == 0) {
condition.await();
}
getString = list.remove(0);
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
return getString;
}
public void put(String stringValue) {
lock.lock();
list.add(stringValue);
condition.signalAll();
lock.unlock();
concurrencySemaphore.release();
}
}
线程类MyThread.java代码如下:
package extthread;
import tools.ListPool;
public class MyThread extends Thread {
private ListPool listPool;
public MyThread(ListPool listPool) {
super();
this.listPool = listPool;
}
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String getString = listPool.get();
System.out.println(Thread.currentThread().getName() + " 取得值 "
+ getString);
listPool.put(getString);
}
}
}
运行类Run.java代码如下:
package test;
import tools.ListPool;
import extthread.MyThread;
public class Run {
public static void main(String[] args) {
ListPool pool = new ListPool();
MyThread[] threadArray = new MyThread[12];
for (int i = 0; i < threadArray.length; i++) {
threadArray[i] = new MyThread(pool);
}
for (int i = 0; i < threadArray.length; i++) {
threadArray[i].start();
}
}
}
这个代码实现了一个简单的资源池管理系统,其中资源池 (ListPool) 中保存了一些可共享的资源(字符串),并通过并发控制工具 Semaphore 和 ReentrantLock 来控制多个线程对资源的并发访问。以下是对代码的分析和功能说明:
1. ListPool 类
ListPool 类是一个资源池管理类,维护了一个包含字符串资源的列表 list,并通过以下几种并发控制机制来管理资源的获取和释放:
• Semaphore concurrencySemaphore:用于限制并发访问的线程数量。该信号量的许可数设置为 5,表示最多允许 5 个线程同时访问资源池。
• ReentrantLock lock 和 Condition condition:用于实现对共享资源列表的同步访问。当资源池为空时,线程会在 condition 上等待,直到有其他线程向资源池中添加资源。
主要方法:
• get(): 从资源池中获取一个资源。
• 通过 concurrencySemaphore.acquire() 获取一个许可,控制并发访问。
• 使用 lock.lock() 获取锁,以确保对 list 的访问是线程安全的。
• 如果资源池为空(list.size() == 0),线程会等待 condition.await(),直到有资源可用。
• 从资源池中移除并返回一个资源。
• 释放锁以允许其他线程访问资源池。
• put(String stringValue): 将一个资源放回资源池。
• 使用 lock.lock() 获取锁,以确保对 list 的访问是线程安全的。
• 将资源添加到资源池中,并通过 condition.signalAll() 通知所有等待的线程资源可用。
• 释放锁。
• 通过 concurrencySemaphore.release() 释放许可,以便其他等待的线程可以继续获取资源。
2. MyThread 类
MyThread 是一个线程类,每个线程会不断地从资源池中获取资源,然后再将资源放回池中。它的 run() 方法中包含了一个无限循环,执行如下步骤:
• 调用 listPool.get() 从资源池中获取一个资源。
• 打印当前线程获取到的资源。
• 调用 listPool.put() 将资源放回资源池。
3. Run 类
Run 类是程序的入口点,主要做以下事情:
• 创建一个 ListPool 对象,初始化资源池。
• 创建并启动 12 个 MyThread 线程,每个线程都共享同一个 ListPool 实例。
代码的实现功能
1. 资源池管理: 资源池 ListPool 维护了一个包含 3 个字符串资源的列表。通过 get() 方法,线程可以从资源池中获取资源,通过 put() 方法将资源放回池中。
2. 并发控制: 使用 Semaphore 限制了同时访问资源池的线程数最多为 5;使用 ReentrantLock 和 Condition 确保在资源不足时,线程会等待并在资源可用时被唤醒。
3. 资源复用: 线程通过不断获取和释放资源,实现了资源的循环利用。
运行效果
当代码运行时,12 个线程会并发地尝试从资源池中获取和释放资源。由于 Semaphore 的许可数是 5,所以最多有 5 个线程能够同时获取资源,其他线程则会等待,直到有线程释放资源后才能继续获取资源。通过这种机制,实现了对有限资源的安全、高效的并发访问和复用。
标签:Java,许可,int,获取,API,线程,Semaphore,资源 From: https://blog.csdn.net/sinat_33536503/article/details/141143544