【多线程】Java多线程基础(11)- Semaphore的使用

344 阅读5分钟

Semaphore(信号量)

什么是Semaphore

Semaphore(信号量)是一种在多线程编程中常用的同步机制,它可以控制对共享资源的访问,以避免多个线程同时访问共享资源而导致的数据竞争和线程安全问题。Semaphore可以看作是一种计数器,它维护了一个可用资源的数量,线程在访问共享资源前需要先获取Semaphore的许可,如果Semaphore的可用资源数量大于0,则线程可以获取许可,访问共享资源并将Semaphore的可用资源数量减1,如果Semaphore的可用资源数量等于0,则线程需要等待,直到有其他线程释放Semaphore的许可,才能获取许可并访问共享资源。

Semaphore通常被用于控制线程的并发访问数量,例如限制线程池中同时执行任务的线程数量,或者限制并发访问某个文件或网络资源的线程数量等。Semaphore可以控制访问共享资源的线程数量,从而避免了多个线程同时访问共享资源而导致的数据竞争和线程安全问题,提高了程序的并发性和执行效率。

使用Semaphore

在Java中,Semaphore是通过java.util.concurrent.Semaphore类来实现的。Semaphore类提供了acquire()release()等方法,用于获取释放Semaphore的许可,以及获取Semaphore的许可数量等操作。Semaphore还提供了一些构造方法,用于创建Semaphore对象,并指定Semaphore的初始许可数量是否使用公平性策略等参数。需要注意的是,在使用Semaphore时,需要保证对共享资源的访问是线程安全的,否则仍会存在数据竞争和线程安全问题。

Semaphore是Java中用于控制并发访问的同步工具,它可以控制对共享资源的访问,并避免多个线程同时访问共享资源而导致的数据竞争和线程安全问题。在Java中,Semaphore可以通过以下步骤来使用:

  1. 创建Semaphore对象,指定Semaphore的许可数量和是否使用公平性策略:
Semaphore semaphore = new Semaphore(permits, fair);

其中,permits表示Semaphore的初始许可数量,fair表示是否使用公平性策略,如果设置为true,则Semaphore会按照线程请求的顺序依次分配许可,否则会随机分配许可。

  1. 在需要访问共享资源的线程中,调用acquire()方法获取Semaphore的许可,访问共享资源并释放许可:
try {
    semaphore.acquire();
    // 访问共享资源
} catch (InterruptedException e) {
    // 处理中断异常
} finally {
    semaphore.release();
}

在上述代码中,acquire()方法用于获取Semaphore的许可,如果Semaphore的许可数量大于0,则线程可以获取许可并访问共享资源,否则线程需要等待,直到有其他线程释放Semaphore的许可。当线程访问完共享资源后,需要调用release()方法释放Semaphore的许可,让其他线程可以继续访问共享资源。

需要注意的是,acquire()方法和release()方法都可能会抛出InterruptedException异常,如果线程在等待Semaphore的许可时被中断,则会抛出InterruptedException异常,需要在catch块中进行处理。

Semaphore还提供了其他一些方法,例如tryAcquire()acquireUninterruptibly()tryAcquire(long timeout, TimeUnit unit)等方法,用于尝试获取Semaphore的许可,或者指定等待时间等操作。

在使用Semaphore时,需要保证对共享资源的访问是线程安全的,并避免死锁和饥饿等问题。需要根据具体的场景和需求选择不同的参数和方法,以实现对共享资源的合理控制和管理。

案例

这个案例是酒店房间预定系统,一共只有两张床,当房间满了之后,其他人就没办法住了。

这个案例就很适合使用Semaphore, 设定一个只能两个线程访问的锁。

import java.util.concurrent.Semaphore;

class Hotel {
    private Semaphore roomSemaphore = new Semaphore(2); // 只有两间房
    public void bookRoom(String guestName) {
        try {
            if(roomSemaphore.tryAcquire()){
                System.out.println(guestName + "预订了房间");
                Thread.sleep(3000); // 模拟客人入住睡觉
                System.out.println(guestName + "退房了");
            }
            else {
                System.out.println(guestName + "想要房间但是没房间了");
                // 这里可以搞一个定时任务,不断更新房间的情况,然后预定
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            roomSemaphore.release(); // 释放许可
        }
    }
}

class Guest implements Runnable {
    private String name;
    private Hotel hotel;

    public Guest(String name, Hotel hotel) {
        this.name = name;
        this.hotel = hotel;
    }

    public void run() {
        hotel.bookRoom(name);
    }
}

public class Main {
    public static void main(String[] args) {
        Hotel hotel = new Hotel();
        Thread thread1 = new Thread(new Guest("Alice", hotel));
        Thread thread2 = new Thread(new Guest("Bob", hotel));
        Thread thread3 = new Thread(new Guest("Charlie", hotel));
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

输出分析

输出:
Bob预订了房间
Alice预订了房间
Charlie想要房间但是没房间了
Alice退房了
Bob退房了

可以看到,当两个人预定房间之后,第三个人再预定就提示房间已满。

我这里使用的是tryAcquire()方法,但它是非阻塞的,因此当没有许可就会返回false; 为此, 当房间有空余之后不会自动去预定房间。

不过,可以使用acquire()方法,这个方法是阻塞式的,当第三个人来订酒店,它会将线程卡在这里,直到有人退房它再去预定房间。

在一些需要快速响应的场景下,例如响应用户请求等,可以使用tryAcquire()方法来尝试获取Semaphore的许可,如果获取失败,则可以立即返回,避免长时间阻塞。在其他一些场景下,如果需要保证访问共享资源的线程不会同时超过一定数量,可以使用acquire()方法来获取Semaphore的许可,确保访问共享资源的线程不会超过指定的数量。