Java并发包提供了哪些并发工具类?

典型回答

我们通常所说的并发包也就是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这种计数器结构,虽然有类似功能,但其实不存在真正意义的持有者,除非我们进行扩展包装。

【完】

猜你喜欢

转载自blog.csdn.net/qweqwruio/article/details/81359769