典型回答
我们通常所说的并发包也就是java.util.concurrent及其子包,集中了Java并发的各种基础工具类,具体主要包括几个方面:
- 提供了比synchronized更加高级的各种同步结构,包括CountDownLatch、CyclicBarrier、Semaphore等,可以实现更加丰富的多线程操作。比如利用Semaphore作为资源控制器,限制同时进行工作的线程数量。
- 各种线程安全的容器,比如最常见的ConcurrentHashMap、有序的ConcurrentSkipListMap,或者通过类似快照机制,实现线程安全的动态数组CopyOnWriteArrayList等。
- 各种并发队列实现,如各种BlockingQueue实现,比较典型的ArrayBlockingQueue、SynchorousQueue或针对特定场景的PriorityBlockingQueue等。
- 强大的Executor框架,可以创建各种不同类型的线程池,调度任务运行等。绝大部分情况下,不再需要自己从头实现线程池和任务调度器。
知识扩展
这部分内容比较多,将分为两讲。这一讲将重点介绍Semaphore,下一讲将介绍CountDownLatch和CyclicBarrier。
1、经典信号量Semaphore
Java提供了经典信号量Semaphore的实现,它通过控制一定数量的许可(permit)的方式,来达到限制通用资源访问的目的。你可以想象一下这个场景:在车站、机场等出租车时,当很多空出租车就位时,为防止过度拥挤,调度员指挥排队等待坐车的队伍一次进来5个人上车,等这5个人坐车出发,再放进去下一批。这和Semaphore的工作原理有些类似。
可以试试使用Semaphore来模拟实现这个调度过程:
import java.util.concurrent.Semaphore;
public class UsualSemaphoreSample {
public static void main(String[] args) throws InterruptedException {
System.out.println("Action...GO!");
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new SemaphoreWorker(semaphore));
t.start();
}
}
public static class SemaphoreWorker implements Runnable {
private String name;
private Semaphore semaphore;
public SemaphoreWorker(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
log("is waiting for a permit!");
semaphore.acquire();
log("acquired a permit!");
log("executed!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log("released a permit!");
semaphore.release();
}
}
private void log(String msg) {
if (name == null) {
name = Thread.currentThread().getName();
}
System.out.println(name + " " + msg);
}
}
}
这段代码是比较典型的Semaphore示例,其逻辑是,线程试图获得工作许可,得到许可则进行任务,然后释放许可,这时等待许可的其它线程,就可获得许可进入工作状态,直到全部处理结束。编译运行,我们就能看到Semaphore的许可机制对工作线程的限制。
但是,从具体节奏看来,其实并不符合我们前面场景的需求,因为本例中Semaphore的用法实际是保证,一直有5个人可以试图乘车,如果有一个人出发了,立即就有排队的人获得许可,而这并不完全符合我们前面的需求。
那么,我们再修改一下,演示个非典型的Semaphore用法。
import java.util.concurrent.Semaphore;
public class AbnormalSemaphoreSample {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new MyWorker(semaphore));
t.start();
}
System.out.println("Action...GO!");
semaphore.release(5);
System.out.println("Wait for permits off");
while (semaphore.availablePermits() != 0) {
Thread.sleep(100L);
}
System.out.println("Action...GO again!");
semaphore.release(5);
}
static class MyWorker implements Runnable {
private Semaphore semaphore;
public MyWorker(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("Executed!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意,上面的代码,更侧重的是演示Semaphore的功能以及局限性,其实有很多线程编程中的反实践。比如使用了sleep来协调任务执行,而且使用轮询调用availiablePermits来检测信号量获取情况。这都是很低效并且脆弱的,通常只是在测试或者诊断场景。
总的来说,我们可以看出Semaphore就是个计数器,其基本逻辑基于acquire/release,并没有太复杂的同步逻辑。
如果Semaphore的数值被初始化为1,那么一个线程就可以通过acquire进入互斥状态。本质上和互斥锁是非常相似的。但是区别也非常明显,比如互斥锁是有持有者的,而对于Semaphore这种计数器结构,虽然有类似功能,但其实不存在真正意义的持有者,除非我们进行扩展包装。
【完】
本文深入探讨了Java并发包中的经典信号量Semaphore,通过实例展示了如何利用Semaphore限制资源访问及其实现多线程间的调度。文章还介绍了Semaphore的基本概念、工作原理及应用场景。
619

被折叠的 条评论
为什么被折叠?



