【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

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

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

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

相关推荐
IT书架4 分钟前
golang面试题
开发语言·后端·golang
初遇你时动了情21 分钟前
uniapp 城市选择插件
开发语言·javascript·uni-app
天天扭码25 分钟前
五天SpringCloud计划——DAY1之mybatis-plus的使用
java·spring cloud·mybatis
程序猿小柒31 分钟前
leetcode hot100【LeetCode 4.寻找两个正序数组的中位数】java实现
java·算法·leetcode
不爱学习的YY酱1 小时前
【操作系统不挂科】<CPU调度(13)>选择题(带答案与解析)
java·linux·前端·算法·操作系统
zongzi_4941 小时前
二次封装的天气时间日历选择组件
开发语言·javascript·ecmascript
丁总学Java1 小时前
Maven项目打包,com.sun.tools.javac.processing
java·maven
kikyo哎哟喂1 小时前
Java 代理模式详解
java·开发语言·代理模式
duration~1 小时前
SpringAOP模拟实现
java·开发语言
小码ssim2 小时前
IDEA使用tips(LTS✍)
java·ide·intellij-idea