边肖将与您分享如何在Java中使用Future和FutureTask。希望大家看完这篇文章后有所收获。我们一起讨论一下吧!
00-1010调用()方法完成后,结果必须存储在主线程已知的对象中,这样主线程才能知道线程返回的结果。您可以为此目的使用“未来”对象。
把Future想象成保存结果的对象——它可能暂时不会保存结果,但将来会保存(一旦Callable返回)。Future基本上是一种主线程可以跟踪其他线程的进度和结果的方式。要实现这个接口,必须重写五个方法,这里列出了重要的方法,比如:
public booleanisdone()
公共Booleanancel(booleanmayinterruptiitfrrunning)用于停止任务。如果尚未启动,它将停止任务。如果启动,只有当“可能中断”为真时,任务才会被中断。
如果任务在正常结束前被取消,则返回true。
public vget()ThrowsInterruptedexception,ExecutionException用于获取任务的结果。如果任务完成,会立即返回结果;否则,它将等待任务完成,然后返回结果。
publicVget(longtimeout,TimeUnitunit)
Throwsinterruptedexception,ExecutionException,TimeoutException如果任务完成,则返回true,否则返回false。
Callable类似于因为它封装了要在另一个线程上运行的任务,而 Future 用于存储从另一个线程获得的结果.的Runnable
实际上,Future也可以和Runnable一起使用。要创建线程,需要Runnable。为了得到结果,你需要未来。
00-1010简介:当一个线程需要等待另一个线程完成一个任务才能继续执行时,此时可以使用FutureTask。假设有多个线程执行几个任务,每个任务最多只能执行一次。当多个线程试图同时执行同一任务时,只允许一个线程执行该任务,其他线程需要等到任务完成后才能继续执行。
Java库有一个特定的FutureTask类型,它实现了Runnable和Future,方便地将这两个功能结合在一起。可以为它通过构造函数提供 Callable 来创建FutureTask。然后,将 FutureTask 对象提供给 Thread 的构造函数以创建Thread 对象.因此,间接地使用 Callable 创建线程.
未来任务状态转换
未来任务有以下七种状态:
未来任务的运行状态,最初是新的。运行状态仅在set、setException和cancel方法中转换为终端状态。在完成过程中,状态可能是瞬时值INTERRUPTING(仅当程序中断以满足**cancel(true)**)或完成(设置结果时)。从中间状态到最终状态的这些转换使用有序/延迟写入,成本较低,因为值是一致的,需要进一步修改。
状态:指示当前任务的运行状态。未来任务的所有方法都在state声明为volatile,州进行,这确保了当对state进行修改时所有的线程都会看到.州的可见性
新:表示新任务,初始状态。
完成:当任务设置为结果,时,它处于完成状态。
,这是一个中间状态。
NORMAL:表示任务正常结束。
EXCEPTIONAL:表示任务因异常而结束
CANCELLED:任务还未执行之前就调用了cancel(true)方法,任务处于CANCELLED
INTERRUPTING:当任务调用cancel(true)中断程序时,任务处于INTERRUPTING状态,这是一个中间状态。
INTERRUPTED:任务调用cancel(true)中断程序时会调用interrupt()方法中断线程运行,任务状态由INTERRUPTING转变为INTERRUPTED
可能的状态过渡:
1、NEW -> COMPLETING -> NORMAL:正常结束
2、NEW -> COMPLETING -> EXCEPTIONAL:异常结束
3、NEW -> CANCELLED:任务被取消
4、NEW -> INTERRUPTING -> INTERRUPTED:任务出现中断
三、使用 Callable 和 Future
Runnable缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。
为了支持此功能,Java 中提供了 Callable 接口。不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable。
所以我们可以找一个中间人,也就是FutureTask。
案例
class MyThreadA implements Callable { @Override public Object call() throws Exception { System.out.println(Thread.currentThread().getName() + "在call方法里"); System.out.println(Thread.currentThread().getName() + "线程进入了 call方法,开始睡觉(进行了一些计算)"); Thread.sleep(10000); System.out.println(Thread.currentThread().getName() + "睡醒了"); return Thread.currentThread().getName() + "返回的:" + System.currentTimeMillis(); } } public class demo1 { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<Integer> futureTaskA = new FutureTask<>(new MyThreadA()); FutureTask<String> futureTaskB = new FutureTask<>(()->{ System.out.println(Thread.currentThread().getName() + "在call方法里"); return Thread.currentThread().getName() + "返回的:" + System.currentTimeMillis(); }); new Thread(futureTaskA,"线程A").start(); new Thread(futureTaskB,"线程B").start(); while (!futureTaskB.isDone()){ //isDone表示FutureTask的计算是否完成 System.out.println("wait......."); } System.out.println(futureTaskA.get()); System.out.println(futureTaskB.get()); System.out.println(Thread.currentThread().getName() + "结束了"); } }
输出结果:
由上图两个线程返回的时间差约等于10秒可以看出,当一个线程(线程B)需要等待(一直wait…)另一个线程(线程A)把某个任务(进行了一些计算)执行完后它才能继续执行,此时可以使用FutureTask。不管futureTaskA.get()和futureTaskB.get()谁在前面,输出结果一定是“线程B返回的:xxx”在“wait…”的后面。假设有多个线程执行若干任务,每个任务最多只能被执行一次。当多个线程试图同时执行同一个任务时,只允许一个线程执行任务,其他线程需要等待这个任务执行完后才能继续执行。
四、小结(FutureTask核心原理)
FutureTask核心原理
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成,当主线程将来需要时,就可以通过 Future对象获得后台作业的计算结果或者执行状态。
• 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果
• 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
• get只计算一次,因此 get 方法放到最后。
附:FutureTask在高并发环境下确保任务只执行一次
网上有篇例子,但是中间讲的不是很清楚。我重新梳理了一下。
在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下面所示:
package com.concurrency.chapter15; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; /** * @program: 错误示例 * * @description: 在很多高并发的环境下,往往我们只需要某些任务只执行一次。 * 这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池, * 当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景, * 通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下 * 在例子中,我们通过加锁确保高并发环境下的线程安全,也确保了connection只创建一次,然而却牺牲了性能。 * * @author: zhouzhixiang * * @create: 2019-05-14 20:22 */ public class FutureTaskConnection1 { private static Map<String, Connection> connectionPool = new HashMap<>(); private static ReentrantLock lock = new ReentrantLock(); public static Connection getConnection(String key) { try { lock.lock(); Connection connection = connectionPool.get(key); if (connection == null) { Connection newConnection = createConnection(); connectionPool.put(key, newConnection); return newConnection; } return connection; } finally { lock.unlock(); } } private static Connection createConnection() { try { return DriverManager.getConnection(""); } catch (SQLException e) { e.printStackTrace(); } return null; } }
看完了这篇文章,相信你对“Java中Future和FutureTask怎么用”有了一定的了解,如果想了解更多相关知识,欢迎关注行业资讯频道,感谢各位的阅读!
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/126943.html