Callable接口入门

是什么?

经典面试题:获得多线程的几种方法? 传统的是继承Thread类和实现runnable接口Java5以后又实现了callable接口和通过Java线程池获得 由上图可知这是一个函数式接口,可以通过lambda表达式或方法引用的赋值对象

与runnable对比

实现方法对比

 创建新类MyThread实现runnable接口
class MyThread implements Runnable{
 @Override
 public void run() {
 }
}
新类MyThread2实现callable接口
class MyThread2 implements Callable{
 @Override
 public Integer call() throws Exception {
  return 200;
 } 
}

两者具体大致三点区别:
Callable和Runnable的差别例如以下:

I    Callable定义的方法是call,而Runnable定义的方法是run。

II   Callable的call方法能够有返回值,而Runnable的run方法不能有返回值。

III  Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。

那我们直接替换runnable是否可行呢?答案是不可行,因为Thread类的构造方法根本没有Callable

FutureTask 原理 FutureTask的类图如下 FutureTask中的构造方法,需要Callable接口,所以可以直接, FutureTask ft = new FutureTask(new MyThread()); new Thread(ft, "AA").start(); 如何获得返回值呢? 可以直接使用ft.get(); 使用FutureTask可以实现异步调用 举个例子 例子: (1)老师上着课,口渴了,去买水不合适,讲课线程继续,我可以单起个线程找班长帮忙买水, 水买回来了放桌上,我需要的时候再去get。 (2)4个同学,A算1+20,B算21+30,C算31*到40,D算41+50,是不是C的计算量有点大啊, FutureTask单起个线程给C计算,我先汇总ABD,最后等C计算完了再汇总C,拿到最终结果

实现原理 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成, 当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。 一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。

只计算一次,get方法放到最后

使用案例

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

class MyThread implements Runnable {

    @Override
    public void run() {

    }
}

class MyThread2 implements Callable {

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


public class CallableDemo {


    public static void main(String[] args) throws Exception {

//FutureTask futureTask = new FutureTask(new MyThread2());
        FutureTask futureTask = new FutureTask(() -> {
            System.out.println(Thread.currentThread().getName() + "  come in callable");
            TimeUnit.SECONDS.sleep(4);
            return 1024;
        });
        FutureTask futureTask2 = new FutureTask(() -> {
            System.out.println(Thread.currentThread().getName() + "  come in callable");
            TimeUnit.SECONDS.sleep(4);
            return 2048;
        });

        new Thread(futureTask, "zhang3").start();
        new Thread(futureTask2, "li4").start();

//System.out.println(futureTask.get());
        //System.out.println(futureTask2.get());
        //1、一般放在程序后面,直接获取结果
        //2、只会计算结果一次

        while (!futureTask.isDone()) {
            System.out.println("***wait");
        }
        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName() + " come over");
    }
}
/**
 * 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,
 * 当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
 * 一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
 * 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,
 * 就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,
 * 然后会返回结果或者抛出异常。
 * 只计算一次
 * get方法放到最后
 */

JUC辅助类讲解

CountDownLatch减少计数

让一些线程阻塞直到另一些线程完成系列操作后才被唤醒 CountDownLatch主要有两个方法,当一个或者多个线程调用await方法时,这些线程会阻塞。其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。

  • 解释:6个同学陆续离开教室后值班同学才可以关门。
  • main主线程必须要等前面6个线程完成全部工作后,自己才能开干
案例代码
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()+"\t 号同学离开教室");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"\t****** 班长关门走人,main线程是班长");
    }
}

使用原理

  • CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
  • 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
  • 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。

CyclicBarrier循环栅栏

  • CyclicBarrier原理
  • 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,
  • 直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
  • 线程进入屏障通过CyclicBarrier的await()方法。_
  • 集齐7颗龙珠就可以召唤神龙_
public class CyclicBarrierDemo
{
    private static final int NUMBER = 7;
    public static void main(String[] args)
    {
        //CyclicBarrier(int parties, Runnable barrierAction)
        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()+"\t 星龙珠被收集 ");
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }, String.valueOf(i)).start();
    }
}
}

Semaphore信号量

在信号量上我们定义两种操作: acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1), 要么一直等下去,直到有线程释放信号量,或超时。 release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。 操作系统中信号量也可以为负数,为负数则意味着有多少线程在等待资源

抢车位案例

public class SemaphoreDemo
{
    public static void main(String[] args)
    {
        Semaphore semaphore = new Semaphore(3);//模拟3个停车位
        for (int i = 1; i<=5;i++)
        {
            new Thread(() -> {
                try
                {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"\t 抢到了车位");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    System.out.println(Thread.currentThread().getName()+"\t------- 离开");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}