Java多线程同步工具类:Semaphore原理剖析

Java多线程同步工具类:Semaphore原理剖析

前驱知识准备:AbstractQueuedSynchronizer队列同步器

[Java多线程之:队列同步器AbstractQueuedSynchronizer原理剖析]

Semaphore原理

Semaphore也就是信号量,提供了资源数量的并发访问控制,可以用于限制访问某些资源(物理或逻辑的)的线程数目。Semaphore是AQS队列同步器中对共享锁的子类实现。Semaphore维护了一个许可证集合,有多少资源需要限制就维护多少许可证集合,假如这里有N个资源,那就对应于N个许可证,同一时刻也只能有N个线程访问。一个线程获取许可证就调用acquire方法,用完了释放资源就调用release方法。

Semaphore核心代码功能使用如下所示:

// ⼀开始有5份共享资源。第⼆个参数表示是否是公平
Semaphore myResources = new Semaphore(5, true);


// 工作线程每获取⼀份资源,就在该对象上记下来
// 在获取的时候是按照公平的方式还是非公平的方式,就要看上⼀行代码的第二个参数了。
// ⼀般⾮公平抢占效率较高。
myResources.acquire();

// 工作线程每归还⼀份资源,就在该对象上记下来
// 此时资源可以被其他线程使⽤
myResources.release();

/*
释放指定数目的许可,并将它们归还给信标。
可用许可数加上该指定数目。
如果线程需要获取N个许可,在有N个许可可用之前,该线程阻塞。
如果线程获取了N个许可,还有可用的许可,则依次将这些许可赋予等待获取许可的其他线程。
*/
semaphore.release(2);

/*
从信标获取指定数⽬的许可。如果可用许可数目不够,则线程阻塞,直到被中断。

该⽅法效果与循环相同,
for (int i = 0; i < permits; i++) acquire();
只不过该方法是原⼦操作。

如果可用许可数不够,则当前线程阻塞,直到:(⼆选⼀)
1. 如果其他线程释放了许可,并且可用的许可数满足当前线程的请求数字;
2. 其他线程中断了当前线程。

permits – 要获取的许可数
*/
semaphore.acquire(3);

Semaphore在争抢资源时的示意图如下图所示,假设有n个线程来获取Semaphore⾥⾯的10份资源(n > 10), n个线程中只有10个线程能获取到,其他线程都会阻塞。直到有线程释放了资源,其他线程才能获取到。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7ePxdyV-1670158909096)(E:\笔记\截图\SemaPhore争抢资源示意图.png)]

当初始的资源个数为1的时候, Semaphore退化为排他锁。正因为如此, Semaphone的实现原理和锁十分类似,是基于AQS,有公平和非公平之分

public void acquire() throws InterruptedException {
	sync.acquireSharedInterruptibly(1);
}
public void release() {
	sync.releaseShared(1);
}

SemaPhore也是使用了队列同步器AbstractQueuedSynchronizer来实现多线程间的同步操作,abstract static class Sync extends AbstractQueuedSynchronizer

acquire()方法通过判断剩余资源数和线程所需资源数的差值是否小于0,如果小于0,则当前线程进行阻塞,如果大于0,对state变量(state变量在AQS中表示同步状态)进行CAS减操作,减到0之后,线程阻塞。在release里对state变量进行CAS加操作。

public class Semaphore {
	protected final boolean tryReleaseShared(int releases) {
	for (;;) {
		int current = getState();
		int next = current + releases;
		if (next < current) // overflow
			throw new Error("Maximum permit count exceeded");
		if (compareAndSetState(current, next))
			return true;
		}
	}
	static final class FairSync extends Sync {
		// ...
		FairSync(int permits) {
		super(permits);
	}
	protected int tryAcquireShared(int acquires) {
		for (;;) {
			if (hasQueuedPredecessors())
				return -1;
			int available = getState();
            // 判断剩余资源数,如果<0,说明资源数不够了,获取资源失败
			int remaining = available - acquires;
			if (remaining < 0 || compareAndSetState(available, remaining))
				return remaining;
			}
		}
	}
}

/**
     * Acquires in shared interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        // addWaiter将当前线程封装成共享节点,放在等待队列尾部
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                // 当前线程前驱节点
                final Node p = node.predecessor();
                if (p == head) {
                    // 尝试获取args数量的资源数,如果资源数不够,则返回<0
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 向后传播唤醒当前节点后的节点
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

关于该方法的更多详细原理剖析,可以看AQS介绍的相关博客:[Java多线程之:队列同步器AbstractQueuedSynchronizer原理剖析](https://blog.csdn.net/Urbanears/article/details/128177063?spm=1001.2014.3001.5502)

实战案例

下面给出一个案例来测试Semaphore的使用:

case: 自习室抢座,写作业:

抢座位的线程:

import java.util.Random;
import java.util.concurrent.Semaphore;

public class MyThread extends Thread {
    private final Semaphore semaphore;
    private final Random random = new Random();
    public MyThread(String name, Semaphore semaphore) {
        super(name);
        this.semaphore = semaphore;
    }
    @Override
    public void run() {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " - 抢座成功,开始写作业");
            Thread.sleep(random.nextInt(1000));
            System.out.println(Thread.currentThread().getName() + " - 作业完成,腾出座位");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        semaphore.release();
    }
}

主方法:

import java.util.concurrent.Semaphore;
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(2);
        for (int i = 0; i < 5; i++) {
            new MyThread("学⽣-" + (i + 1), semaphore).start();
        }
    }
}

上面主方法中,调用new Semaphore(2)定义了2份共享资源,或者两份许可证,也就是同一时刻最多只允许2个线程执行。输出结果:

学⽣-1 - 抢座成功,开始写作业
学⽣-2 - 抢座成功,开始写作业
学⽣-2 - 作业完成,腾出座位
学⽣-3 - 抢座成功,开始写作业
学⽣-1 - 作业完成,腾出座位
学⽣-4 - 抢座成功,开始写作业
学⽣-3 - 作业完成,腾出座位
学⽣-5 - 抢座成功,开始写作业
学⽣-5 - 作业完成,腾出座位
学⽣-4 - 作业完成,腾出座位

出结果:

学⽣-1 - 抢座成功,开始写作业
学⽣-2 - 抢座成功,开始写作业
学⽣-2 - 作业完成,腾出座位
学⽣-3 - 抢座成功,开始写作业
学⽣-1 - 作业完成,腾出座位
学⽣-4 - 抢座成功,开始写作业
学⽣-3 - 作业完成,腾出座位
学⽣-5 - 抢座成功,开始写作业
学⽣-5 - 作业完成,腾出座位
学⽣-4 - 作业完成,腾出座位
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

JermeryBesian

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

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

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

打赏作者

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

抵扣说明:

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

余额充值