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();
}
}
}
0 Comments
Leave a comment