尚硅谷JUC高并发编程学习笔记Callable,FutureTask,JUC辅助类

Posted exodus3

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了尚硅谷JUC高并发编程学习笔记Callable,FutureTask,JUC辅助类相关的知识,希望对你有一定的参考价值。

一、Callable接口

创建线程的多种方式:

继承Thread类
实现Runnable接口
Callable接口
线程池

目前学习了有两种创建线程的方法,一种是通过创建 Thread 类,另一种是通过使用 Runnable 创建线程,但是,Runnable 缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。为了支持此功能,Java 中提供了 Callable 接口

比较Runnable接口和Callable接口

  • Callable中的call()计算结果,如果无法计算结果,会抛出异常
  • Runnable中的run()使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用该对象的run方法
  • 总的来说:run()没有返回值,不会抛出异常。而call()有返回值,会抛出异常

两个接口代码

//实现Runnable接口
class MyThread1 implements Runnable 
    @Override
    public void run() 

    


//实现Callable接口
class MyThread2 implements Callable 

    @Override
    public Integer call() throws Exception 
        return 200;
    

具体在主函数中,通过Thread线程创建接口
只有这个可以new Thread(new MyThread1(),"AA").start();
这个不可以new Thread(new MyThread2(),"BB").start();
因为Thread的构造函数中没有Callable接口的参数设置
直接替换不可以,只能用下面这种线程创建方法
(找一个类,即和Runnable接口有关系,又和Callable接口有关系)

发现Runnable接口有实现类FutureTask(中间对象)
FutureTask的构造函数有Callable参数,通过FutureTask创建线程对象。

二、FutureTask

FutureTask的构造方法有

  • FutureTask(Callable<> callable) 创建一个FutureTask,一旦运行就执行给定的Callable
  • FutureTask(Runnable runnable,V result)创建一个FutureTask,一旦运行就执行给定的Runnable那边了,并安排成功完成时get返回给定的结果

其他常用的代码:
get()获取结果
isDone()判断是否计算结束

FutureTask 具体实现方法可以有两种方式(此处用的泛型参数)
第一种是

//实现Callable接口
class MyThread2 implements Callable 

    @Override
    public Integer call() throws Exception 
        return 200;
    


FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());

第二种是

//lambda表达式
FutureTask<Integer> futureTask2 = new FutureTask<>(()->
    System.out.println(Thread.currentThread().getName()+" come in callable");
    return 1024;
);

创建线程new Thread(futureTask2,"lucy").start();
获取结果System.out.println(futureTask2.get());

所谓的FutureTask是在不影响主任务的同时,开启单线程完成某个特别的任务,之后主线程续上单线程的结果即可(该单线程汇总给主线程只需要一次即可)。
如果之后主线程再开启该单线程,可以直接获得结果,因为之前已经执行过一次了。

具体完整代码案例

//比较两个接口
//实现Runnable接口
class MyThread1 implements Runnable 
    @Override
    public void run() 

    


//实现Callable接口
class MyThread2 implements Callable 

    @Override
    public Integer call() throws Exception 
        System.out.println(Thread.currentThread().getName()+" come in callable");
        return 200;
    


public class Demo1 
    public static void main(String[] args) throws ExecutionException, InterruptedException 
        //Runnable接口创建线程
        new Thread(new MyThread1(),"AA").start();

        //Callable接口,报错
       // new Thread(new MyThread2(),"BB").start();

        //FutureTask
        FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());

        //lam表达式
        FutureTask<Integer> futureTask2 = new FutureTask<>(()->
            System.out.println(Thread.currentThread().getName()+" come in callable");
            return 1024;
        );

        //创建一个线程
        new Thread(futureTask2,"lucy").start();
        new Thread(futureTask1,"mary").start();

//        while(!futureTask2.isDone()) 
//            System.out.println("wait.....");
//        
        //调用FutureTask的get方法
        System.out.println(futureTask2.get());

        System.out.println(futureTask1.get());

        System.out.println(Thread.currentThread().getName()+" come over");
       

执行结果

lucy come in callable
mary come in callable
1024
200
main come over

三、JUC强大辅助类

该辅助类主要讲述三个
分别为:
减少计数CountDownLatch
循环栅栏CyclicBarrier
信号灯Semaphore

1、CountDownLatch

该类的构造方法为
CountDownLatch(int count)构造一个用给定计数初始化CountDownLatch

两个常用的主要方法
await() 使当前线程在锁存器倒计数至零之前一直在等待,除非线程被中断。
countDown()递减锁存器的计数,如果计数达到零,将释放所有等待的线程。

CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句 。
具体步骤可以演化为定义一个类,减1操作,并等待到0,为0执行结果

通过具体的案例进行加深代码理解
6个同学陆续离开教室之后,班长才能锁门
如果不加 CountDownLatch类,会出现线程混乱执行,同学还未离开教室班长就已经锁门了。

public class CountDownLatchDemo 
    //6个同学陆续离开教室之后,班长锁门
    public static void main(String[] args) throws InterruptedException 
        
        //6个同学陆续离开教室之后
        for (int i = 1; i <=6; i++) 
            new Thread(()->
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");

            ,String.valueOf(i)).start();
        

        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
    

执行结果

1 号同学离开了教室
5 号同学离开了教室
4 号同学离开了教室
2 号同学离开了教室
main 班长锁门走人了
3 号同学离开了教室
6 号同学离开了教室

具体正确的案例代码

//演示 CountDownLatch
public class CountDownLatchDemo 
    //6个同学陆续离开教室之后,班长锁门
    public static void main(String[] args) throws InterruptedException 

        //创建CountDownLatch对象,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        //6个同学陆续离开教室之后
        for (int i = 1; i <=6; i++) 
            new Thread(()->
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");

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

            ,String.valueOf(i)).start();
        

        //等待
        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
    

1 号同学离开了教室
5 号同学离开了教室
4 号同学离开了教室
3 号同学离开了教室
2 号同学离开了教室
6 号同学离开了教室
main 班长锁门走人了

2、CyclicBarrier

该类是一个同步辅助类,允许一组线程互相等到,直到到达某个公共屏障点,在设计一组固定大小的线程的程序中,这些线程必须互相等待,这个类很有用,因为barrier在释放等待线程后可以重用,所以称为循环barrier。

常用的构造方法有:
CyclicBarrier(int parties,Runnable barrierAction)创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动barrier时执行给定的屏障操作,该操作由最后一个进入barrier的线程操作。

常用的方法有:
await()在所有的参与者都已经在此barrier上调用await方法之前一直等待。

通过具体案例
集齐7颗龙珠就可以召唤神龙
完整代码

//集齐7颗龙珠就可以召唤神龙
public class CyclicBarrierDemo 

    //创建固定值
    private static final int NUMBER = 7;

    public static void main(String[] args) 
        //创建CyclicBarrier
        CyclicBarrier cyclicBarrier =
                new CyclicBarrier(NUMBER,()->
                    System.out.println("*****集齐7颗龙珠就可以召唤神龙");
                );

        //集齐七颗龙珠过程
        for (int i = 1; i <=7; i++) 
            new Thread(()->
                try 
                    System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");
                    //等待
                    cyclicBarrier.await();
                 catch (Exception e) 
                    e.printStackTrace();
                
            ,String.valueOf(i)).start();
        
    

执行结果

1 星龙被收集到了
6 星龙被收集到了
4 星龙被收集到了
5 星龙被收集到了
2 星龙被收集到了
7 星龙被收集到了
3 星龙被收集到了
*****集齐7颗龙珠就可以召唤神龙

总结:
CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行。CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行。cyclicBarrier.await()之后的语句。可以将 CyclicBarrier 理解为加 1 操作。

3、Semaphore

一个计数信号量,从概念上将,信号量维护了一个许可集,如有必要,在许可可用前会阻塞每一个acquire(),然后在获取该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。

具体常用的构造方法有:
Semaphore(int permits)创建具有给定的许可数和非公平的公平设置的Semapore。

具体常用的方法有:
acquire()从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断 release()释放一个许可,将其返回给信号量。

设置许可数量Semaphore semaphore = new Semaphore(3);
一般acquire()都会抛出异常,releasefinally中执行

通过具体案例
6辆汽车,停3个车位
完整代码:

//6辆汽车,停3个车位
public class SemaphoreDemo 
    public static void main(String[] args) 
        //创建Semaphore,设置许可数量
        Semaphore semaphore = new Semaphore(3);

        //模拟6辆汽车
        for (int i = 1; i <=6; i++) 
            new Thread(()->
                try 
                    //抢占
                    semaphore.acquire();

                    System.out.println(Thread.currentThread().getName()+" 抢到了车位");

                    //设置随机停车时间
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));

                    System.out.println(Thread.currentThread().getName()+" ------离开了车位");
                 catch (InterruptedException e) 
                    e.printStackTrace();
                 finally 
                    //释放
                    semaphore.release();
                
            ,String.valueOf(i)).start();
        
    

执行结果:

1 抢到了车位
3 抢到了车位
2 抢到了车位
3 ------离开了车位
4 抢到了车位
1 ------离开了车位
5 抢到了车位
5 ------离开了车位
6 抢到了车位
2 ------离开了车位
6 ------离开了车位
4 ------离开了车位

以上是关于尚硅谷JUC高并发编程学习笔记Callable,FutureTask,JUC辅助类的主要内容,如果未能解决你的问题,请参考以下文章

尚硅谷JUC高并发编程学习笔记Callable,FutureTask,JUC辅助类

尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口

尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口

尚硅谷JUC高并发编程学习笔记

尚硅谷JUC高并发编程学习笔记

尚硅谷JUC高并发编程学习笔记