java模拟多线程 工具_Java常用多线程辅助工具---semaphore

前言

semaphore是常用的Java多线程工具之一,中文意思是信号的意思。此类的主要作用是限制线程并发的数量,通过发放许可方式控制线程数量,可以说该类是synchronized关键字的升级版本,比synchronized功能更强大。

入门

通过semaphore实现一个线程的同步

@Slf4j

class ThreadA extends Thread{

private Service service;

public ThreadA(Service service){

super();

this.service = service;

}

@Override

public void run() {

service.testMethod();

}

}

class ThreadB extends Thread{

private Service service;

public ThreadB(Service service){

super();

this.service = service;

}

@Override

public void run() {

service.testMethod();

}

}

class ThreadC extends Thread{

private Service service;

public ThreadC(Service service){

super();

this.service = service;

}

@Override

public void run() {

service.testMethod();

}

}

@Slf4j

class Service{

private Semaphore semaphore = new Semaphore(1);

public void testMethod(){

try {

semaphore.acquire();

log.info("线程名称{},beginTime:{}",Thread.currentThread().getName(),System.currentTimeMillis());

Thread.sleep(5000);

log.info("线程名称{},endTime:{}",Thread.currentThread().getName(),System.currentTimeMillis());

semaphore.release();

} catch (InterruptedException e) {

log.error("线程错误{}",e.getMessage());

}

}

}

上面写了三个线程调用使用semaphore的服务,下面把线程启动一下:

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();

}

我是通过main方法去启动的线程,如果用junit去启动的话,发现线程会启动不了(现在还没有搞懂是什么原因)。

上面通过只用了一个许可,以为这只允许一个线程执行acquire和release之间的代码,所以无论执行多少遍,结果都是下面的结果(类似于同步):

90b3b604da605a3d0b8105cc48f6e42e.png

常用API

类Semaphore的构造参数permit设置是许可的个数,上面的例子是在一个许可下的情况,如果多个许可的话,那么acquire和release之间可以同时允许多个线程执行,其实构造参数中设置的许可只是初始化的许可,如果我们做下面这个操作:

public static void main(String[] args) throws InterruptedException {

Semaphore semaphore = new Semaphore(5);

semaphore.acquire();

semaphore.acquire();

semaphore.acquire();

semaphore.acquire();

semaphore.acquire();

log.info("可用的许可有多少:{}",semaphore.availablePermits());

semaphore.release();

semaphore.release();

semaphore.release();

semaphore.release();

semaphore.release();

semaphore.release();

log.info("可用的许可有多少:{}",semaphore.availablePermits());

semaphore.release(4);

log.info("可用的许可有多少:{}",semaphore.availablePermits());

}

看看运行结果:

353d28af367cd62eae1602915cda0b13.png

发现可用的许可越来越多,可见通过release(int)是可以动态增加许可的。

acquireUninterruptibly:指的是获取的许可不允许被终端,在线程运行的时候,如果调用线程的interrupt,线程并不会抛出异常,并停止运行。

availablePermits:可以获取当前可用的许可还有多少个。

drainPermits:可获取并返回立即可用的所有许可个数,并且将可用的许可置0。

getQueueLength:获取等待许可的线程个数。

hasQueuedThreads:判断当前有没有线程在等待这个许可。

刚刚在开始的那个案例中,我们发现线程运行的顺序是A、B、C,其实线程的启动顺序是abc,造成的原因是许可默认是公平的,即先启动的线程大概率先拿到许可,我们可以通过在构造函数的第二个参数设置是否公平许可:

7eeb1b1669c4d4fee274a5ef64861366.png

开启非公平许可后的执行结果是下面这样的:

6093dd52b7b34e1980724d4ff3e240f7.png

tryAcquire:无阻塞的尝试获取许可,如果获取不到就返回false,程序继续往下走。

实现一个字符串池

semaphore既然可以控制并发 的数量,这种功能可以用在pool技术中,比如一个字符串池,若干个线程可以同时访问池中的数据,但是同时只有其中的几个可以获得数据,使用完毕以后再放回池中,很多pool技术的实现都是这种思路吧,开始上代码,所以创建一个池:

class StrPool{

private int poolSize = 3;

private Semaphore semaphore = new Semaphore(10); //许可数量设置成和池大小一致

private ReentrantLock lock = new ReentrantLock();

private Condition condition = lock.newCondition();

private List pool = new ArrayList();

public StrPool() {

for (int i = 0; i < poolSize; i++) {

pool.add("str---" + (i + 1));

}

}

public String get(){

String str = null;

try {

semaphore.acquire(); //获取许可

lock.lock(); //同步锁

while (pool.isEmpty()) {

System.out.println("------is waiting now ------- ");

condition.await(); //如果池中没有字符串了,那就线程挂着,等有人放进新的字符串后 唤醒该线程

}

str = pool.remove(0);

lock.unlock();

} catch (InterruptedException e) {

e.printStackTrace();

}

return str == null ? "" : str;

}

public void put(String str){

lock.lock();

pool.add(str);

condition.signalAll(); //如果有在等待的线程,进行唤醒

lock.unlock();

semaphore.release();

}

}

特意把许可数设置成大于池的大小,为了观察是不是有的线程进入了等待状态,我们再起写个线程去操作字符串池:

@Slf4j

class PoolThread extends Thread{

private StrPool pool;

public PoolThread(StrPool pool) {

this.pool = pool;

}

@Override

public void run() {

String str = pool.get();

log.info("线程{},获取了池中的数据{}",Thread.currentThread().getName(),str);

pool.put(str);

}

}

启动20个线程,看看运行结果:

public static void main(String[] args) throws InterruptedException {

StrPool strPool = new StrPool();

PoolThread[] threads = new PoolThread[20];

for (int i = 0; i < 20; i++) {

threads[i] = new PoolThread(strPool);

}

for (int i = 0; i < 20; i++) {

threads[i].start();

}

}

我们看到一进来就有的线程就在的等待状态了。然后获取的结果就是池中的 1 到 3编号的字符串:

bc23f1c976ba7e34f97daf293434f077.png

我们常用的数据库连接池,其实可以这么实现,原理大同小异。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值