【JavaEE】Callable,Semaphore和CountDownLatch

🔥个人主页: 中草药

🔥专栏:【Java】登神长阶 史诗般的Java成神之路


一.Callable

在Java中,Callable接口是一个可以返回结果的异步任务执行方式。它与Runnable接口类似,都是描述一个"任务",但最主要的区别在于Callable描述的是带返回值的任务,Runnable描述的是不带返回值的任务


Callable 通常需要搭配 FutureTask 来使⽤. FutureTask ⽤来保存 Callable 的返回结果. 因为Callable 往往是在另⼀个线程中执行的, 啥时候执行完并不确定.
FutureTask 就可以负责这个等待结果出来的⼯作.

想象去吃麻辣烫. 当餐点好后, 后厨就开始做了. 同时前台会给你⼀张 "小票" . 这个小票就是
FutureTask. 后面我们可以随时凭这张小票去查看自己的这份麻辣烫做出来了没.

接口定义

Callable接口位于java.util.concurrent包中,其定义如下:

java 复制代码
public interface Callable<V> {
    V call() throws Exception;
}

这里的关键点是call()方法,它会抛出异常,这意味着你可以在call()方法中处理任何需要的逻辑,并可能抛出异常。

使用Callable和Future

使用Callable通常涉及到以下几个步骤:

  1. 创建Callable对象

    首先,你需要创建一个实现了Callable接口的类或者匿名内部类,并重写call()方法。

  2. 获取FutureTask对象

    提交任务后,你会得到一个Future对象,该对象可以用来获取计算的结果或检查任务的状态。

  3. 从FutureTask获取结果

    使用Future.get()方法来获取计算的结果。需要注意的是,这将阻塞调用线程直到结果可用。

下面是一个简单的例子:

java 复制代码
ublic static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum=0;
                for (int i = 0; i < 1000; i++) {
                    sum+=i;
                }
                return sum;
            }
        };

        FutureTask<Integer> futureTask=new FutureTask<>(callable);
        Thread t=new Thread(futureTask);
        t.start();

        // 后续需要通过 FutureTask 拿到最终的结果.
        System.out.println(futureTask.get());
    }

注意事项

  • Callable任务可能会抛出异常,这些异常需要被捕获处理。
  • Future.get()方法是阻塞的,如果不想阻塞主线程,可以使用Future.get(long timeout, TimeUnit unit)来设置超时时间。

二.Semaphore

在Java中,Semaphore是一个用于控制同时访问特定资源的线程数量的同步工具。它可以用来管理一组相关许可的集合。每个许可都代表对某个资源的一次访问权限。

构造函数

  • Semaphore(int permits):创建具有给定数量的许可证的Semaphore
  • Semaphore(int permits, boolean fair):创建具有给定数量的许可证的Semaphore,并指定是否公平地获取许可证。

主要方法

获取许可证

  • acquire():获取一个许可证,在没有可用许可证之前一直等待。
  • acquireUninterruptibly():获取一个许可证,在没有可用许可证之前一直等待,即使线程被中断也不会立即返回。
  • tryAcquire():尝试获取一个许可证,如果许可证可用则立即返回true,否则返回false。
  • tryAcquire(long timeout, TimeUnit unit):尝试获取一个许可证,如果在给定的时间内许可证可用,则返回true,否则返回false。

释放许可证

  • release():释放一个许可证,返回一个许可证到池中。

查询状态

  • availablePermits():返回当前可用的许可证数量。
  • drainPermits():获取所有可用的许可证,并返回获取的许可证数量。
  • hasQueuedThreads():如果有线程正在等待获取许可证,则返回true。
  • getQueueLength():返回正在等待获取许可证的线程数。

示例

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(3);

        semaphore.acquire();
        System.out.println("申请资源");

        semaphore.acquire();
        System.out.println("申请资源");

        semaphore.acquire();
        System.out.println("申请资源");

        semaphore.acquire();
        System.out.println("申请资源");
    }

如上代码运行结果为

如运行结果所示,尽管申请了四次资源,但semaphore做了限制是3,因此只会显示三次打印

如若,测试以下代码

java 复制代码
public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(3);

        semaphore.acquire();
        System.out.println("申请资源");

        semaphore.acquire();
        System.out.println("申请资源");

        semaphore.acquire();
        System.out.println("申请资源");

        semaphore.release();
        System.out.println("释放资源");

        semaphore.acquire();
        System.out.println("申请资源");
    }

结果为

注意事项

  • 在使用Semaphore时,一定要确保在完成资源访问后调用release()方法来释放许可证,否则会导致其他线程永远无法获取许可证。
  • 如果不希望线程因等待许可证而被中断,可以使用acquireUninterruptibly()
  • 如果希望在等待获取许可证时能够响应中断信号,应使用acquire()方法,并在catch块中处理InterruptedException
  • Semaphore可以用于多种场景,比如控制并发请求的数量、限制数据库连接池的大小等。

三.CountDownlatch

CountDownLatch是Java并发包java.util.concurrent中的一个实用工具类,它允许一个或多个线程等待其他线程完成操作。简单来说,CountDownLatch可以看作是一个计数器,当计数器的值减到0时,等待的线程就会被释放继续执行。

构造函数

  • CountDownLatch(int count):创建一个新的CountDownLatch,初始化计数器为给定的值。

主要方法

  1. 减少计数器

    • void countDown():将计数器的值减1。当计数器的值变为0时,所有等待的线程都会被释放。
  2. 等待计数器变为0

    • void await():使当前线程等待,直到其他线程调用countDown()方法使计数器的值减到0。

    • boolean await(long timeout, TimeUnit unit):使当前线程等待,直到其他线程调用countDown()方法使计数器的值减到0,或者等待时间超过给定的超时时间。

  3. 查询计数器值

    • long getCount():返回当前计数器的值。

示例

下面是一个简单的使用CountDownLatch的例子,结合线程池展示了如何使用CountDownLatch来等待一些子线程完成它们的工作。

java 复制代码
public static void main(String[] args) throws InterruptedException {
        ExecutorService service= Executors.newFixedThreadPool(4);

        // 构造方法的数字, 就是拆分出来的任务个数.
        CountDownLatch countDownLatch=new CountDownLatch(20);

        for (int i = 0; i < 20; i++) {
            int id=i;
            service.submit(()->{
                System.out.println("任务"+id+"开始执行");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务"+id+"结束执行");
                countDownLatch.countDown();
            });
        }

        // 当 countDownLatch 收到了 20 个 "完成" , 所有的任务就都完成了.
        // await => all wait
        // await 这个词也是计算机术语. 在 python / js 意思是 async wait (异步等待)
        countDownLatch.await();

        System.out.println("所有任务已完成");
    }

注意事项

  • 当调用await()方法时,当前线程会等待,直到计数器的值变为0。如果当前线程在此期间被中断,那么它将抛出InterruptedException,并且计数器的值不会改变。
  • 如果你想设置一个等待的超时时间,可以使用await(long timeout, TimeUnit unit)方法。如果超时时间到了,线程将继续执行,但计数器的值仍然不变。
  • CountDownLatch只能用于一次性事件。一旦计数器的值减为0,就不能再重置。如果需要多次使用,可以考虑使用CyclicBarrier

🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀

以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐

制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸

相关推荐
考虑考虑17 小时前
Jpa使用union all
java·spring boot·后端
用户37215742613518 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊19 小时前
Java学习第22天 - 云原生与容器化
java
渣哥20 小时前
原来 Java 里线程安全集合有这么多种
java
间彧20 小时前
Spring Boot集成Spring Security完整指南
java
间彧21 小时前
Spring Secutiy基本原理及工作流程
java
Java水解1 天前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆1 天前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学1 天前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole1 天前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端