JUC系列(四) callable与 常用的工具类

简介: 在多线程工作中常用的一些辅助类
📣 📣 📣 📢📢📢
☀️☀️你好啊!小伙伴,我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。
📒 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️,擅长使用Java技术开发web项目和工具
📒 文章内容丰富:覆盖大部分java必学技术栈,前端,计算机基础,容器等方面的文章
📒 如果你也对Java感兴趣,关注小冷吧,一起探索Java技术的生态与进步,一起讨论Java技术的使用与学习
✏️高质量技术专栏专栏链接: 微服务数据结构netty单点登录SSMSpringCloudAlibaba
😝公众号😝想全栈的小冷,分享一些技术上的文章,以及解决问题的经验
当前专栏JUC系列

Callable

image-20220302112839751

callable是什么?

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不容 run() /call()
入门案例

查看源码 我们发现 Thread 类所有的方法参数都是 Runnable 那我们怎么样可以使用callble呢?

image-20220302113326707

思路图:

image-20220302113344057

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread(new Runnable()).start();
        //new Thread(new FutureTask<V>()).start();
        //new Thread(new FutureTask<V>(callable)).start();
        //怎么启动callable
        new Thread().start();
        MyThread thread = new MyThread();
        //适配类
        FutureTask futureTask = new FutureTask(thread);
        new Thread(futureTask, "A").start();

        Integer o = (Integer) futureTask.get();
        System.out.println(o);
    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 123;
    }
}
细节
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread(new Runnable()).start();
        //new Thread(new FutureTask<V>()).start();
        //new Thread(new FutureTask<V>(callable)).start();
        //怎么启动callable
        new Thread().start();
        MyThread thread = new MyThread();
        //适配类
        FutureTask futureTask = new FutureTask(thread);
        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start();

        // get 接口 可能会产生阻塞
        Integer o = (Integer) futureTask.get();
        System.out.println(o);
    }

这里我们用两个对象,他会输出几次?

image-20220302114220524

一次,为什么呢? 源码会告诉我们答案

 public void run() {
  //这里的判断 如果state 不是new 那么就会return   
        if (state != NEW ||
            !RUNNER.compareAndSet(this, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

很明显,我们两次开启的线程但是futureTask只会有一个线程run完,新开的话就会检测到状态不是new 就会return

常用辅助类

CountDownLatch

线程减法计数器 递减

image-20220302135542300

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

常用方法

public void countDown()

递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少.

public boolean await(long timeout,TimeUnit unit) throws InterruptedException

使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回true值。

  如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直出于休眠状态, 由于调用countDown()方法,计数到达零;或者其他某个线程中断当前线程;或者已超出指定的等待时间。

  • 如果计数到达零,则该方法返回true值。
  • 如果当前线程,在进入此方法时已经设置了该线程的中断状态;或者在等待时被中断,则抛出InterruptedException,并且清除当前线程的已中断状态。
  • 如果超出了指定的等待时间,则返回值为false。如果该时间小于等于零,则该方法根本不会等待。
简单入门案例
//计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //    总数是六
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "go out");
                //数量-1
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        //等待计数器归零
        countDownLatch.await();
        System.out.println("Close door");
    }
}

countDownLatch.countDown(); // 数量-1

countDownLatch.await(); //等待计数器归零

每次有线程调用countDown 数量 -1 假设计数器变为0,countDownLatch.await()就会被唤醒;继续执行

CyclicBarrier

线程 计数器 递增,是函数式接口

构造器
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)

parties 是参与线程的个数

第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务

重要方法
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException

解析:

  • 线程调用 await() 表示自己已经到达栅栏
  • BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时
代码案例
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /*集齐七龙珠 召唤神龙
         * 召唤龙珠线程
         * Cyclicbarrier 是函数式接口 ,所以我们可以直接用 lamba
         * */
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙成功");
        });

        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集" + temp + "个龙珠");
                //等待
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

SemaPhore

Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

举例理解

可以把它简单的理解成我们停车场入口立着的那个显示屏,每有一辆车进入停车场显示屏就会显示剩余车位减1,每有一辆车从停车场出去,显示屏上显示的剩余车辆就会加1,当显示屏上的剩余车位为0时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止。

image-20220302142104753

使用场景

通常用于那些资源有明确访问数量限制的场景,常用于限流 。

比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。

比如:停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。

常用方法
acquire()  
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。

acquire(int permits)  
获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
    
acquireUninterruptibly() 
获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
    
tryAcquire()
尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。

tryAcquire(long timeout, TimeUnit unit)
尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。

release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。

hasQueuedThreads()
等待队列里是否还存在等待线程。

getQueueLength()
获取等待队列里阻塞的线程数。

drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。

availablePermits()
返回可用的令牌数量。
代码案例
public class SemaPhoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                //得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放车位
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}
相关文章
|
7月前
|
Java
JAVA JUC Callable 接口
【1月更文挑战第5天】JAVA JUC Callable 接口
|
2月前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
38 0
|
4月前
|
缓存 Java
JUC(4)Callable和常用的辅助类
这篇文章介绍了Java并发工具包中的`Callable`接口及其与`Runnable`的区别,以及几个常用的并发辅助类,如`CountDownLatch`、`CyclicBarrier`和`Semaphore`,并提供了它们使用方式的示例代码。
|
7月前
|
Java 程序员
Java多线程基础-16:简述Java并发编程JUC中的Callable接口
Callable接口是Java中用于描述带有返回值任务的接口,与Runnable相对,后者无返回值。Callable的call()方法用于执行具体任务并返回结果。
98 0
|
监控 安全 Java
自旋锁的伪代码实现,CAS的ABA问题,JUC常见类:Callable,ReentrantLock,线程创建方法的总结,信号量,原子类的应用场景,特定场所的组件CountDomLatch,针对集合类的
自旋锁的伪代码实现,CAS的ABA问题,JUC常见类:Callable,ReentrantLock,线程创建方法的总结,信号量,原子类的应用场景,特定场所的组件CountDomLatch,针对集合类的
|
存储 Java
《JUC并发编程 - 基础篇》 Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架(三)
《JUC并发编程 - 基础篇》 Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架(一)
《JUC并发编程 - 基础篇》 Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架(三)
|
存储 监控 安全
《JUC并发编程 - 基础篇》 Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架(二)
《JUC并发编程 - 基础篇》 Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架
《JUC并发编程 - 基础篇》 Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架(二)
《JUC并发编程 - 基础篇》 Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架(一)
《JUC并发编程 - 基础篇》 Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架
《JUC并发编程 - 基础篇》 Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架(一)
|
设计模式 缓存 Java
JUC并发编程学习(七)-Callable学习
JUC并发编程学习(七)-Callable学习
JUC并发编程学习(七)-Callable学习
|
7月前
|
存储 Java
高并发编程之多线程锁和Callable&Future 接口
高并发编程之多线程锁和Callable&Future 接口
89 1