一、Java实现多线程的三种方式

方式一:继承Thread类:

public class Test extends Thread {
    public static void main(String[] args) {
        Thread t = new Test();
        t.start();
    }
    @Override
    public void run() {
        System.out.println("Override run() ...");
    }
}

方式二:实现Runnable接口,并覆写run方法:

public class Test implements Runnable {
    public static void main(String[] args) {
        Thread t = new Thread(new Test());
        t.start();
    }
    @Override
    public void run() {
        System.out.println("Override run() ...");
    }
}

我们今天来学习另外一种创建多线程的方式:实现Callable接口,并覆call方法

方式三:实现Callable接口,并覆call方法:

//1.创建一个线程类,实现Callable接口,
//2.Callable会报一个黄色的警告,原因:可以加个泛型,这个泛型,是返回值对应的类型。
//3.我们这个题是产生随机数,所以加个泛型Integer
public class RanDomCallable implements Callable<Integer> {
    //4.一旦上面的泛型确定了,那么这个重写的方法的返回值类型就是Integer了。
    public Integer call() throws Exception {
        // 睡眠2秒
        Thread.sleep(2000);
        return new Random().nextInt(10);
    }
    //5.写main方法测试
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //6.创建一个线程对象:
        RanDomCallable rdc=new RanDomCallable();
        //7.启动线程,直接用Thread传rdc不行,必须再借助一个FutureTask
        FutureTask<Integer> ft=new FutureTask<Integer>(rdc);
        Thread t=new Thread(ft);//FutureTask实现了Runnable接口所以可以传入
        t.start();
        //8.上面已经将线程启动了,直接运行是没有结果的
        //9.我们必须要对返回值处理,那么如何接收返回值:
        Integer i = ft.get();//get方法中可以加入sleep,验证get是个阻塞方法
        //10.判断线程是否执行结束:
        System.out.println(ft.isDone());
        System.out.println(i);
        System.out.println(ft.isDone());
    }
}

上面代码的执行结果是:每两秒输出一个随机数,原因就是get方法是阻塞的啊,要执行完才会得到结果的。

我们看看FutureTask的源码:

(1)属性

// 底层线程执行的状态标识,具体的数值就是下面int类型的数量
private volatile int state;
// 正常的状态只有用到0,1,2,程序从0->1->2线程就执行完了
// 线程走完,最终state的数值,从1(new)变为2(completing)变为3(normal) 那么线程就执行好了!
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED  = 6;

/** The underlying callable; nulled out after running */
// 要执行的任务
private Callable<V> callable;
/** The result to return or exception to throw from get() */
// 返回值存储在这里
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread running the callable; CASed during run() */
// 执行任务的线程
private volatile Thread runner;
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;

(2)构造方法

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    // 初始状态state = 0
    this.state = NEW;       // ensure visibility of callable
}

(3)run方法

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            // 初始状态时state == NEW == 0
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    // 调用call()
                    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);
        }
}

(4)set方法

protected void set(V v) {
        // 状态从0->1(COMPLETING)
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            // 返回值放入outcome中
            outcome = v;
            // 状态从1->2(NORMAL)
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
}

(5)get方法

public V get() throws InterruptedException, ExecutionException {
        int s = state;
        // 如果状态是<=1的,那么,就进入awaitDone(死循环)
        // 什么时候状态变更为2,什么时候return
        // 所以会阻塞在这里,要一直等待状态变为2
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        // 调用report方法,传入state = 2
        return report(s);
}
@SuppressWarnings("unchecked")
    private V report(int s) throws ExecutionException {
        Object x = outcome;
        // 只有当state == NORMAL,才会将返回值返回
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
}

二、Callable和Future出现的原因

创建线程的2种方式:

一种是直接继承Thread;

另外一种就是实现Runnable接口。

​ 这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。从Java 1.5开始提供了Callable和Future两个接口,通过使用它们可以在任务执行完毕后得到执行结果。

三、Callable和Future介绍

Callable接口代表一段可以调用并返回结果的代码;

Future接口表示异步任务,是还没有完成的任务给出的未来结果。

所以说Callable用于产生结果,Future用于获取结果。

Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法在线程池中执行Callable内的任务。由于Callable任务是并行的(并行就是整体看上去是并行的,其实在某个时间点只有一个线程在执行),我们必须等待它返回的结果。
java.util.concurrent.Future对象为我们解决了这个问题。在线程池提交Callable任务后返回了一个Future对象,使用它可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法(阻塞的方法)让我们可以等待Callable结束并获取它的执行结果。

也就是说Future提供了三种功能:

  1)判断任务是否完成;

  2)能够中断任务;

  3)能够获取任务执行结果。

  因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask,FutureTask实现了RunnableFuture接口。FutureTask已经在上面介绍过了,这里就不赘述了。

参考链接

本片文章,主要整理自互联网,便于自己复习知识所用,以下为参考链接!

【1】Java程序员必须掌握的线程知识-Callable和Future

【2】Callable原理,线程池执行Callable任务
【3】Java多线程之Callable接口及线程池

Java线程的创建方式三:Callable(四)的更多相关文章

  1. 【多线程】线程创建方式三:实现callable接口

    线程创建方式三:实现callable接口 代码示例: import org.apache.commons.io.FileUtils; import java.io.File; import java. ...

  2. Java多线程——线程的创建方式

    Java多线程——线程的创建方式 摘要:本文主要学习了线程的创建方式,线程的常用属性和方法,以及线程的几个基本状态. 部分内容来自以下博客: https://www.cnblogs.com/dolph ...

  3. Java之解决线程安全问题的方式三:Lock锁

    import java.util.concurrent.locks.ReentrantLock; /** * 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增 * * 1. 面试题:sy ...

  4. Java线程:创建与启动

    Java线程:创建与启动 一.定义线程   1.扩展java.lang.Thread类.   此类中有个run()方法,应该注意其用法: public void run() 如果该线程是使用独立的 R ...

  5. 漫谈并发编程(二):java线程的创建与基本控制

    java线程的创建 定义任务           在java中使用任务这个名词来表示一个线程控制流的代码段,用Runnable接口来标记一个任务,该接口的run方法为线程运行的代码段. public ...

  6. JAVA - 线程从创建到死亡的几种状态都有哪些?

    JAVA - 线程从创建到死亡的几种状态都有哪些? 新建( new ):新创建了一个线程对象. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 sta ...

  7. java线程(1)——三种创建线程的方式

    前言 线程,英文Thread.在java中,创建线程的方式有三种: 1.Thread 2.Runnable 3.Callable 在详细介绍下这几种方式之前,我们先来看下Thread类和Runnabl ...

  8. Java线程实现的第三种方式Callable方式与结合Future获取返回值

    多线程的实现方式有实现Runnable接口和继承Thread类(实际上Thread类也实现了Runnable接口),但是Runnable接口的方式有两个弊端,第一个是不能获取返回结果,第二个是不能抛出 ...

  9. java多线程 -- 创建线程的第三者方式 实现Callable接口

    Java 5.0 在 java.util.concurrent 提供了一个新的创建执行线程的方式:Callable 接口Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个 ...

随机推荐

  1. SkylineGlobe for web开发是否支持IE11?

    之前有客户反馈,说在IE11里浏览skyline开发的系统页面,会提示错误,怀疑是不是skyline不支持IE11了,其实不是. 主要是因为IE11更加遵循W3C规范,所以IE11与低版本IE在加载a ...

  2. Unity编辑器:自定义编辑器样式——GUIStyle

    通过GUIStyle,可以自定义Unity编辑器的样式. GUIStyle可以new一个全新的实例,这样,需要自己处理所有自己需要的效果. GUIStyle还可以基于已经存在的实例new一个新的实例, ...

  3. 面试笔记--Fast-Fail(快速失败)机制

    1.解决: fail-fast机制,是一种错误检测机制.它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生.若在多线程环境下使用fail-fast机制的集合,建议使用“java. ...

  4. 【出错记录】Tomcat非root用户启动无法拥有权限读写文件

    简单记录下,如有必要,将深入补充: 一.非root用户运行Tomcat及原因 由于项目中,为了安全需要,Tomcat将禁止以root形式启动,原因很简单,举个例子,一旦有人恶意将jsp文件透过某个别的 ...

  5. IT江湖--这个冬天注定横尸遍野

    今年江湖大事繁起,又至寒冬,冻的不仅是温度,更是人心. 这两天上班途中看到多个公众号和媒体发了很多 "XXX公司裁员50%" 等等诸如此类的文章,也真是撼动人心.寒冬,比以往来的更 ...

  6. Javascript 小练习

    --------------------------要收获别人五年才能收获的东西,你就要做好准备,遭受别人五人所遭受的坎坷” // -------------------------------*** ...

  7. SQL Server(2000,2005,2008):恢复/回滚时间比预期长(译)

    我已经讨论了各种确定恢复状态的方法,但是本周我参与了一个围绕回滚的有趣讨论.交易已经运行了14个小时,然后发出了KILL SPID.SPID进入回滚,并发生2天和4小时. 自然的问题是为什么不14小时 ...

  8. Leetcode-645 Set Mismatch

    The set S originally contains numbers from 1 to n. But unfortunately, due to the data error, one of ...

  9. javascript与php与python的函数写法区别与联系

    1.javascript函数写法种类: (一).第一种 function test(param){ return 111; } (二).第二种 var test = function(param){ ...

  10. Day7 Ubantu学习(一)

    Linux是多用户操作系统 Ubantu学习参考网址:https://www.cnblogs.com/resn/p/5800922.html 1.虚拟机网络类型的理解 bridged(桥接模式) : ...