Semaphore的使用

本文详细介绍了Java中的Semaphore类,探讨了其如何控制线程并发数量,包括Semaphore的基本概念、常见API使用方法,以及通过案例演示Semaphore在多线程环境下的工作原理。

Semaphore类

简介
   Semaphore类所提供的功能完全是synchorized关键字的升级版,但是它提供的功能更加的强大与方便,==主要作用就是控制线程并发的数量==,但是这一点,是synchorized所做不到的
常见API:

在这里插入图片描述

使用
   单词Semaphore的中文含义就是信号、信号系统,此类的主要作用的就是限制线程并发的数量,如果不限制现场并发的数量,则CPU资源很快被耗尽,每个线程执行的任务是相当缓慢。因为CPU要把时间片分配给不同的线程对象,而且上下文切换也要耗时,最终造成系统运行效率大幅降低。
   Smaphore类发放许可的计算方式是“减法”操作。
1、Semaphore类的同步性
   多线程中的同步概念其实就是排队执行任务,执行任务是一个一个执行,不能并行执行,优点是有助于程序逻辑的正确性,不会出现非线程安全的问题,保证软件功能的稳定性。
   现在使用一个案例来看Semaphore是如何限制并发数的:
public class Service {
    //permits为1代表同一时间内,最多允许一个线程同时执行acquire()和release()之间的代码
    private Semaphore semaphore = new Semaphore(1);
    public void testMethod(){
        try {
            //使用1个许可,是减法操作
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName()+"begin timer="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName()+"end timer="+System.currentTimeMillis());
            semaphore.release();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}


public class ThreadA extends Thread {

    private Service service;

    public ThreadA(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.testMethod();
    }
}

public class ThreadB extends Thread {

    private Service service;

    public ThreadB(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.testMethod();
    }
}

public class ThreadC extends Thread {

    private Service service;

    public ThreadC(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.testMethod();
    }
}

public class Run {

    
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        ThreadC c = new ThreadC(service);
        c.setName("C");

        a.start();
        b.start();
        c.start();
    }
}

运行结果:

在这里插入图片描述
由此可看到3个线程是同步的

2、API 方法的使用
2.1、方法acquire(int permits)参数作用及动态添加permits许可数量
有参方法acquire(int permits)的功能是每调用1次该方法,就使用x个许可
代码示例如下:
public class Service {

	private Semaphore semaphore = new Semaphore(10);
	
	public void testMethod() {
		try {
			semaphore.acquire(2);
			System.out.println("Test");
			Thread.sleep(5000);
			semaphore.release(2);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

如果多次调用Semaphore类的release()或者release(int)方法时,还可以动态增加permits的个数,例如:

public class Run {

	public static void main(String[] args) {
		Semaphore semaphore = new Semaphore(5);
		try {
			semaphore.acquire();
			semaphore.acquire();
			semaphore.acquire();
			semaphore.acquire();
			semaphore.acquire();
			System.out.println(semaphore.availablePermits());
			semaphore.release();
			semaphore.release();
			semaphore.release();
			semaphore.release();
			semaphore.release();
			semaphore.release();
			System.out.println(semaphore.availablePermits());
			semaphore.release(4);
			System.out.println(semaphore.availablePermits());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

在这里插入图片描述
如结果所示:new Semaphore(5)中的5并不是最终的许可证数量,仅仅是初始的状态值。

2.2、方法acquireUninterruptibly的使用
方法acquireUninterruptibly()的作用是使等待进入acquire()方法的线程,不允许被中断。
中断案例:
public class Service {

	private Semaphore semaphore = new Semaphore(1);
	
	public void testMethod() {
		try {
			semaphore.acquire();
			System.out.println(Thread.currentThread().getName()+" begin timer="+System.currentTimeMillis());
			for(int i=0;i<10000/50;i++) {
				System.out.println(i);
				Thread.sleep(1000);
				
			}
			System.out.println(Thread.currentThread().getName()+" end timer="+System.currentTimeMillis());
			semaphore.release();
		} catch (InterruptedException e) {
			System.out.println("线程"+Thread.currentThread().getName()+"进入了catch");
			e.printStackTrace();
		}
	}
}
public class Run {

	public static void main(String[] args)  {
		Service service  = new Service();
		ThreadA a = new ThreadA(service);
		a.setName("A");
		a.start();
		ThreadB b = new ThreadB(service);
		b.setName("B");
		b.start();
		a.interrupt();
		System.out.println("main中断了a");
	}
}

public class ThreadA extends Thread {

	private Service service;
	
	public ThreadA(Service service) {
		super();
		this.service = service;
	}
	@Override
	public void run() {
		service.testMethod();
	}
}
public class ThreadB extends Thread {

	private Service service;
	
	public ThreadB(Service service) {
		super();
		this.service = service;
	}
	@Override
	public void run() {
		service.testMethod();
	}
}

运行结果如图:
在这里插入图片描述
不可中断案例:
将Service中的acquire()修改成acquireUninterruptibly()方法,其他代码无需修改

public class Service {

	private Semaphore semaphore = new Semaphore(1);
	
	public void testMethod() {
		try {
		    //此处修改为不可中断获取
			semaphore.acquireUninterruptibly();
			System.out.println(Thread.currentThread().getName()+" begin timer="+System.currentTimeMillis());
			for(int i=0;i<10000/50;i++) {
				System.out.println(i);
				Thread.sleep(1000);
				
			}
			System.out.println(Thread.currentThread().getName()+" end timer="+System.currentTimeMillis());
			semaphore.release();
		} catch (InterruptedException e) {
			System.out.println("线程"+Thread.currentThread().getName()+"进入了catch");
			e.printStackTrace();
		}
	}
}

结果如下:
在这里插入图片描述

在此我们可以看到当使用acquireUninterruptibly和acquire的使用场景是不一样的,acquire是等待许可的过程中允许中断的,acquireUninterruptibly是等待许可的过程中不允许中断的,acquireUninterruptibly()以及acquireUninterruptibly(int permits)作用是一样的,如果成功获取锁,则取得指定的permits许可个数,具体使用哪一个取决于我们业务逻辑和场景。
2.3、方法availablePermits()和drainPermits()的使用
availablePermits()返回Semaphore对象中当前可用的许可数,此方法通常用于开发调试,因为许可的数量是实时在变化,不是一个固定的值
drainPermits()可以获取并返回立即可用的所有许可个数,并且将可用许可置为0

案例如下:

public class MyService {

	private Semaphore semaphore = new Semaphore(10);
	
	public void testMethod() {
		try {
			semaphore.acquire();
			System.out.println(semaphore.availablePermits());
			System.out.println(semaphore.drainPermits()+" "+semaphore.availablePermits());
			System.out.println(semaphore.drainPermits()+" "+semaphore.availablePermits());
			System.out.println(semaphore.drainPermits()+" "+semaphore.availablePermits());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			semaphore.release();
		}
	}
}
public class Run {

	public static void main(String[] args)  {
		MyService service = new MyService();
		service.testMethod();
	}
}

结果如图所示:
在这里插入图片描述

从结果我们可以发现:
drainPermits()会一次性消费所有的许可,因此当我们不想其他线程同时去消费许可,可以使用此方法来提前消费
2.4、方法getQueueLength()和hasQueueThreads()方法的使用
方法getQueueLength()的作用是取得等待许可的线程个数
方法hasQueueThreads()的作用是判断有没有线程在等待许可
这两个方法通常都是在判断当前有没有等待许可的线程信息时使用

案例如下:

public class MyService {
	
	private Semaphore semaphore  = new Semaphore(1);
	
	public void testMethod() {
		try {
			semaphore.acquire();
			Thread.sleep(1000);
			System.out.println("还有大约:"+semaphore.getQueueLength()+"个线程在等待");
			System.out.println("是否有线程正在等待信号量呢?"+semaphore.hasQueuedThreads());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
			semaphore.release();
		}
	}

}
public class MyThread  extends Thread{
	
	private MyService service;
	public MyThread(MyService service) {
		super();
		this.service = service;
	}
	
	@Override
	public void run() {
		service.testMethod();
	}

}

public class Run {

	public static void main(String[] args) {
		MyService service = new MyService();
		MyThread firstThread = new MyThread(service);
		firstThread.start();
		
		MyThread[] threadArray = new MyThread[4];
		for(int i=0;i<4;i++) {
			threadArray[i] = new MyThread(service);
			threadArray[i].start();
		}
	}
}

结果如图所示:
在这里插入图片描述

2.5、方法tryAcquire()及其重载方法的使用
无参方法tryAcquire()的作用是尝试地获取1个许可,如果获取不到则返回false,此方法通常与if语句结合使用,其具有无阻塞的特点。无阻塞的特点可以使线程不至于在同步处一直持续等待的状态,如果if语句判断不成立则线程会继续走else语句,程序会继续运行。
有参方法tryAcquire(int permits)的作用是尝试地获取x个许可,如果获取不到则返回false。
有参方法tryAcquire(int long timeout,TimeUnit unit)的作用是在指定的时间内尝试获取1个许可,如果获取不到则返回false。
有参方法tryAcquire(long permits,long timeout,TimeUnit unit)的作用是在指定的时间内尝试地获取x个许可,如果获取不到则返回false。

案例如下:

public class Service {
	private Semaphore semaphore = new Semaphore(3);
	
	public void testMethod() {
		try {
			if (semaphore.tryAcquire(3, 3, TimeUnit.SECONDS)) {
				System.out.println("ThreadName="+Thread.currentThread().getName()+"首选进入!");
				for (int i = 0; i < Integer.MAX_VALUE; i++) {
					
				}
				semaphore.release();
			}else {
				System.out.println("ThreadName="+Thread.currentThread().getName()+"未成功进入!");
			}
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}
public class Run {

	public static void main(String[] args) {
		Service service = new Service();
		ThreadA a = new ThreadA(service);
		a.setName("A");
		a.start();
		
		ThreadB b = new ThreadB(service);
		b.setName("B");
		b.start();
		
	}
}

结果如图所示:
在这里插入图片描述

2.6、公平和非公平信号量
所谓的公平信号量是获得锁的顺序与线程启动的顺序有关,但不代表100%地获得信号量,仅仅是在概率上能得到保证,而非公平信号量则与其无关。

案例如下:
非公平信号量:

public class MyService {
	private boolean isFair = false;
	private Semaphore semaphore = new Semaphore(1,isFair);
	
	public void testMethod() {
		try {
			semaphore.acquire();
			System.out.println("ThreadName="+Thread.currentThread().getName());
		} catch (InterruptedException e) {
			
			e.printStackTrace();
		}finally {
			semaphore.release();
		}
	}

}
public class MyThread  extends Thread{

	private MyService myService;
	
	public MyThread(MyService myService) {
		super();
		this.myService = myService;
	}
	@Override
	public void run() {
		System.out.println("ThreadName="+this.getName()+"启动了!");
		myService.testMethod();
	}
}

public class Run {

	public static void main(String[] args) {
		MyService service = new MyService();
		MyThread firstThread = new MyThread(service);
		firstThread.start();
		
		MyThread[] threadArray = new MyThread[4];
		for (int i = 0; i < 4; i++) {
			threadArray[i] = new MyThread(service);
			threadArray[i].start();
		}
	}
}

结果如图:
在这里插入图片描述

非公平信号量是线程启动的顺序和调用semaphore.acquire()的顺序无关,也就是线程先启动了并不代表获得许可
修改MyService中的代码如下:
private boolean isFair = true;

运行结果如图:
在这里插入图片描述

此现象说明即使是公平信号量,也不能保证线程启动顺序和semaphore.acquire()强一致性

在此,Semaphore常用的API已经基本学习完成了,后续将继续学习Exchanger的使用,加油!!!
<think>好的,我需要帮助用户了解如何使用Semaphore进行并发控制,并给出示例代码。首先,我要回忆Semaphore的基本概念和工作原理。根据引用中的信息,Semaphore通过信号量控制并发数量,acquire减少许可,release增加许可,超过容量的线程需要等待。 接下来,用户需要具体的使用方法和示例代码。引用里提到了Python的asyncio.SemaphoreJava的示例。用户可能对Python或Java感兴趣,但问题中没有明确指定,所以我应该覆盖两种常见语言,比如Python和Java,或者选一个主要的。但根据用户提供的引用,引用1是Python的,引用3、4、5是Java的。可能用户希望看到两种语言的示例?或者需要明确询问?但根据用户最后的提问,直接要求示例代码,可能需要多语言示例。 不过,根据系统指令,用户不可见层需要生成相关问题,而用户可见层需要结构清晰的回答。我应该先解释Semaphore的基本使用步骤,然后分语言给出示例,并引用相关文献。 需要确保代码语法正确,Python使用async with,Java使用acquire和release。同时注意引用标记,比如引用1和引用4。然后生成相关问题。 现在组织内容:首先介绍Semaphore的作用,然后分步骤说明使用方法,接着给出Python和Java的代码示例,每个示例后附上引用来源。最后生成相关问题。 检查是否符合所有系统指令:行内公式用$...$,但这个问题可能不涉及数学公式。独立公式不需要。中文回答,结构清晰,正确引用,生成相关问题。确认无误后输出。</think>### Semaphore 的并发控制使用方法 #### 1. 基本使用步骤 - **创建 Semaphore 对象**:指定最大并发数量(许可数)[^2][^4] - **获取许可**:在需要控制并发的代码块前调用 `acquire()` 或通过上下文管理器进入 - **释放许可**:在代码块执行完成后调用 `release()` 或通过上下文管理器自动释放 #### 2. Python 示例(异步场景) ```python import asyncio async def task(semaphore, id): async with semaphore: # 自动获取/释放许可 print(f"任务{id}开始执行") await asyncio.sleep(1) print(f"任务{id}执行完成") async def main(): semaphore = asyncio.Semaphore(3) # 最大并发3 tasks = [task(semaphore, i) for i in range(5)] await asyncio.gather(*tasks) asyncio.run(main()) ``` *引用自异步编程实现[^1]* #### 3. Java 示例(线程同步) ```java import java.util.concurrent.Semaphore; public class Demo { private static final Semaphore semaphore = new Semaphore(3); // 最大并发3 public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(() -> { try { semaphore.acquire(); // 获取许可 System.out.println("任务开始:" + Thread.currentThread().getId()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); // 释放许可 System.out.println("任务完成:" + Thread.currentThread().getId()); } }).start(); } } } ``` *引用自线程并发控制实现[^4]* #### 4. 关键机制 - 当许可耗尽时,后续线程进入阻塞状态 - 公平性可通过构造函数参数配置(Java 支持公平锁)[^4] - 支持尝试获取许可(`tryAcquire`)和超时机制[^5] §§ 相关问题 §§ 1. Semaphore 与 Mutex 锁有什么区别? 2. 如何防止 Semaphore 的许可泄漏问题? 3. Semaphore 在数据库连接池中的应用? 4. 如何实现 Semaphore 的跨进程控制? 5. Semaphore 的公平模式对性能有什么影响? : Python异步信号量实现 [^2]: Semaphore基础工作机制 : Java并发控制示例 : 单许可信号量实现
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

升仔聊编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值