- Callable 与 Future
- Callable 与 Runnable 类似,但是有返回值。
- 类型参数是返回值的类型。Callable接口的方法名为 call() 。
- 如 Callable<Integer>表示最终返回 Integer对象 的异步计算。
- Future保存异步计算的结果。可以启动一个计算,将Future对象交给某个线程。
public interface Future<V>
{
V get() throws ...; //调用被阻塞 直到计算完成
V get(long timeout, TimeUnit unit ) throws ...; //超时抛出TimeoutException异常
//如果运行该计算的线程被中断,以上两个方法都抛出 InterruptedException异常
void cancel(boolean mayInterrupt);
//如果没开始计算,直接取消。如果计算运行中且mayInterrupt为true,将计算中断。
boolean isCancelled();
boolean isDone(); //计算完成与否
}
- FutureTask包装器 是一种很便利的机制,可将Callable转换成 Future 和 Runnable,它同时实现二者的 接口。
- 示例程序:
package future;
import java.io.*;
import java.util.concurrent.*;
import java.util.*;
public class FutureTest {
public static void main(String[] args) {
try(Scanner in = new Scanner(System.in)) {
System.out.println("Enter directory: (e.g. /user/local/jdk5.0/src)");
String directory = in.nextLine();
System.out.println("Enter keyword to match:");
String keyword = in.nextLine();
MatchCounter counter = new MatchCounter(new File(directory), keyword);
FutureTask<Integer> task = new FutureTask<>(counter);
Thread t = new Thread(task);
t.start();
try {
System.out.println( task.get() + " matching files.");
}catch(ExecutionException e ) {
e.printStackTrace();
}catch(InterruptedException e ) {
}
}
}
}
package future;
import java.util.concurrent.*;
import java.io.*;
import java.util.*;
public class MatchCounter implements Callable<Integer> {
private String keyword;
private File directory;
public MatchCounter( File directory, String keyword ) {
this.directory = directory;
this.keyword = keyword;
}
public Integer call() {
int count = 0;
try {
ArrayList<Future<Integer>> results = new ArrayList<>();
File[] files = directory.listFiles();
for( File file : files ) {
if( file.isDirectory() ) {
MatchCounter matchCounter = new MatchCounter(file, keyword);
FutureTask<Integer> task = new FutureTask<>(matchCounter);
results.add(task);
Thread t = new Thread(task);
t.start();
}else {
if( search(file) ) count++;
}
}
for( Future<Integer> result : results ) {
try {
count += result.get();
}catch(ExecutionException e) {
e.printStackTrace();
}
}
}catch(InterruptedException e) {
}
return count;
}
public boolean search(File file) {
try {
try(Scanner in = new Scanner(file, "UTF-8")){
boolean found = false;
while( in.hasNextLine() ) {
String line = in.nextLine();
if( line.contains(keyword)) found = true;
}
return found;
}
}catch(IOException e) {
e.printStackTrace();
return false;
}
}
}
- 执行器
- 构建一个新的线程是有一定代价的,因为涉及与操作系统的交互。如果程序中建立了大量的生命期很短的线程,应该使用线程池(Thread Pool)。
- 一个线程池中包含许多准备运行的空闲线程。将Runable对象交给线程池,就会有一个线程调用run方法。当run方法退出时,线程不会死亡,而是在池中准备为下一个请求提供服务。
- 使用线程池的另一个理由:减少并发线程的数目。创建大量的线程会大大降低性能甚至使虚拟机崩溃。
- 执行器(Executor)类有许多静态工厂方法用来构建线程池
- newCachedThreadPool 必要时创建新线程;空闲线程会被保留60s
- newFixedThreadPool 包含固定数量的线程;空闲线程会被一直保留
- newSingleThreadExecutor 只有一个线程的“池”,该线程顺序执行每一个提交的任务
- newScheduledThreadPool 用于预定执行而构建的固定线程池
- newSingleScheduledExecutor 用于预定执行而构建的单线程“池”
- 线程池
- newCachedThreadPool 创建的线程池,对于每个任务,如果有空闲线程可用,立刻让它执行任务,否则,创建新线程。
- newFixedThreadPool 创建一个具有固定大小的线程池。如果提交的任务数大于空闲线程数,那么把得不到服务的任务放置在队列中。
- newSingleThreadExecutor 是一个退化大小为1的线程池,提交顺序执行任务。
- 以上三个方法返回 实现了 ExecutorService接口 的 ThreadPoolExecutor类 对象。
- 可用下面的方法之一将一个Runnable对象 或 Callable对象 提交给 ExecutorService
- Future<?> submit(Runnable task)
- Future<T> submit(Runnable task, T result)
- Future<T> submit(Callable<T> task)
- 关闭线程池
- shutdown方法:启动线程池的关闭序列,不再接受新的任务,当所有任务都完成后,线程池中的线程死亡。
- shutdownNow :取消尚未开始的任务,并试图中断正在运行的线程。
- 示例程序:
package threadpool;
import java.io.*;
import java.util.concurrent.*;
import java.util.*;
public class ThreadPoolTest {
public static void main(String[] args) {
try(Scanner in = new Scanner(System.in)) {
System.out.println("Enter a directory:");
String directory = in.nextLine();
System.out.println("Enter a keyword:");
String keyword = in.nextLine();
ExecutorService pool = Executors.newCachedThreadPool();
MatchCounter task = new MatchCounter(new File(directory), keyword, pool);
Future<Integer> result = pool.submit(task);
try {
System.out.println( result.get() + " matching files.");
}catch( ExecutionException e ) {
e.printStackTrace();
}catch( InterruptedException e ) {
}
pool.shutdown();
int largest = ( (ThreadPoolExecutor) pool ).getLargestPoolSize();
System.out.println("largest pool size = " + largest );
}
}
}
package threadpool;
import java.util.concurrent.Callable;
import java.io.*;
import java.util.concurrent.*;
import java.util.*;
public class MatchCounter implements Callable<Integer> {
private String keyword;
private File directory;
private ExecutorService pool;
public MatchCounter(File directory, String keyword, ExecutorService pool ) {
this.directory = directory;
this.keyword = keyword;
this.pool = pool;
}
public Integer call() {
int count = 0;
try {
ArrayList<Future<Integer>> results = new ArrayList<>();
File[] files = directory.listFiles();
for( File file : files ) {
if( file.isDirectory() ) {
MatchCounter counter = new MatchCounter(file, keyword, pool);
Future<Integer> result = pool.submit(counter);
results.add(result);
}else {
if( search(file) ) count++;
}
}
for( Future<Integer> result : results ) {
try {
count += result.get();
}catch( ExecutionException e ) {
e.printStackTrace();
}
}
}catch( InterruptedException e ) {
}
return count;
}
public boolean search(File file) {
try {
try(Scanner in = new Scanner(file, "UTF-8")){
boolean found = false;
while( !found && in.hasNextLine() ) {
String line = in.nextLine();
if( line.contains(keyword) ) found = true;
}
return found;
}
}catch(IOException e) {
e.printStackTrace();
return false;
}
}
}
- 预定执行
- ScheduledExecutorService接口 具有为 预定执行 或 重复执行任务 而设计的方法。Executors类的 newScheduledThreadPool方法 和 newSingleThreadScheduledExecutor方法 将返回实现了 ScheduledExecutorService接口 的对象。
- 可以控制延迟之后,固定周期执行任务,或者 固定调用完成到下一次调用开始的延迟间隔。
- 控制任务组,使用 执行器 更有实际意义的原因:
- shutdownNow取消所有任务
- invokeAny方法,提交所有对象到一个Callable对象的集合中,并返回某个已经完成了的任务的结果。(只需某一种解决方法的情况下适用)
- invokeAll 执行所有,返回所有执行方案的结果的列表
- 构建 ExecutorCompletionService可以将 结果按可获得的顺序保存 。(更高效)
- java.util.concurrent.ExecutorCompletionService
- ExecutorCompletionService(Executor e)
- Future<V> submit(Callable<V> task)
- Future<V> submit( Runnable task, V result )
- Future<V> take() //移除下一个已完成的结果,如果没有任何已完成的结果可用则阻塞
- Future<V> poll() //溢出下一个已完成的结果,如果没有则返回null
- Future<V> poll(long time, TimeUnit unit)
- java.util.concurrent.ExecutorCompletionService
- Fork-Join框架
- 有些应用使用了大量的线程,但其中大多数都是空闲的。另一些应用可能使用多个线程来完成计算密集型任务,Fork-Join框架用于此。
if( problemSize < threshold )
solve problem directly
else{
break problem into subproblems
recursively solve each subproblem
combine the result
}
- 要采用框架可用的一种方式完成这种递归计算,需要提供一个 扩展RecursiveTask<T>的类(计算生成类型为T的结果)或者提供一个 扩展RecursiveAction的类(如果不生成任何结果),再覆盖compute方法来生成并调用子任务,然后合并结果。
- 示例程序:1000 0000个随机浮点数(0.0~1.0)统计 >0.5 的总个数。
package forkjoin;
import java.util.concurrent.*;
import java.util.function.DoublePredicate;
public class ForkJoinTest {
public static void main(String[] args) {
final int SIZE = 1000_0000;
double[] data = new double[SIZE];
for( int i = 0; i < SIZE; i++ )
data[i] = Math.random();
Counter counter = new Counter(data, 0, data.length, x -> x > 0.5);
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(counter);
System.out.println( counter.join() );
}
}
class Counter extends RecursiveTask<Integer>{
public static final int THRESHOLD = 1000;
private double[] data;
private int from;
private int to;
private DoublePredicate filter;
public Counter( double[] data, int from, int to, DoublePredicate filter ) {
this.data = data;
this.from = from;
this.to = to;
this.filter = filter;
}
@Override
protected Integer compute() {
if( from - to < THRESHOLD ) {
int count = 0;
for( int i = from; i < to; i++ ) {
if( filter.test(data[i]) ) count++;
}
return count;
}else {
int center = (from + to)/2;
Counter first = new Counter(data, from, center, filter);
Counter second = new Counter(data, center, to, filter);
invokeAll(first, second);
return first.join() + second.join();
}
}
}
- 可完成Future
- 处理非阻塞调用的传统方法是使用事件处理器,为任务完成之后注册一个处理器。
- Java SE 8的 CompletableFuture类 提供了一种候选方法。与 事件处理器 不同, “可完成future” 可以 “组合”。
- 为CompletableFuture<T>对象增加一个动作
- thenApply T -> U
- thenCompose T -> CompletableFuture<U>
- handle (T, Throwable) -> U
- thenAccept T->void
- whenComplete (T, Throwable) -> void
- thenRun Runnable
- 组合多个组合对象
- thenCombine
- thenAcceptBoth
- runAfterBoth
- applyToEither
- acceptEither
- runAfterEither
- static allOf
- static anyOf
- 同步器
- ...
- 线程与Swing
- Swing不是线程安全的。如果你试图在多个线程中操纵用户界面的元素,那么用户界面可能崩溃。
- 将线程与Swing一起使用时,遵从两个简单的原则
- 如果一个动作耗时长,在一个独立的工作器线程中做这件事不要在 事件分配线程 中做。
- 除了事件分配线程,不要在任何线程中接触Swing组件。(单一线程规则)
- java.awt.EventQueue
- static void invokeLater(Runnable runnable) //在待处理的线程被处理后,让runnable对象的run方法在事件分配线程中执行
- static void invokeAndWait(Runnable runnable) //该调用会阻塞,直到run方法终止。
- static boolean isDispatchThread()
- 实例程序:
package swingthread;
import javax.swing.JFrame;
import javax.swing.*;
import java.util.*;
import java.awt.*;
public class SwingThreadFrame extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(()->{
SwingThreadFrame frame = new SwingThreadFrame();
frame.setTitle("Swing Thread Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
public SwingThreadFrame() {
JButton goodButton = new JButton("Good");
JButton badButton = new JButton("Bad");
JComboBox<Integer> box = new JComboBox<>();
box.insertItemAt(Integer.MAX_VALUE, 0);
box.setPrototypeDisplayValue(box.getItemAt(0));
box.setSelectedIndex(0);
goodButton.addActionListener(event->{
GoodWorkerRunnable r = new GoodWorkerRunnable(box);
Thread t = new Thread(r);
t.start();
});
badButton.addActionListener(event->{
BadWorkerRunnable r = new BadWorkerRunnable(box);
Thread t = new Thread(r);
t.start();
});
JPanel panel = new JPanel();
panel.add(goodButton);
panel.add(badButton);
panel.add(box);
add(panel);
pack();
}
class GoodWorkerRunnable implements Runnable{
private JComboBox<Integer> box;
private Random generator;
public GoodWorkerRunnable( JComboBox<Integer> box) {
this.box = box;
this.generator = new Random();
}
public void run() {
try {
while(true) {
EventQueue.invokeLater(()->{
int i = Math.abs( generator.nextInt() );
if( i % 2 == 0 ) {
box.insertItemAt(i, 0);
}else if( box.getItemCount() > 0 ) {
box.removeItemAt(i%box.getItemCount());
}
});
Thread.sleep(1);
}
}catch( InterruptedException e ) {
}
}
}
class BadWorkerRunnable implements Runnable{
private JComboBox<Integer> box;
private Random generator;
public BadWorkerRunnable( JComboBox<Integer> box) {
this.box = box;
this.generator = new Random();
}
public void run() {
try {
while(true) {
int i = Math.abs( generator.nextInt() );
if( i%2 == 0 ) {
box.insertItemAt(i, 0);
}else if( box.getItemCount() > 0 ){
box.removeItemAt(i%box.getItemCount());
}
Thread.sleep(1);
}
}catch(InterruptedException e ) {
}
}
}
}
- 使用Swing工作线程
- SwingWorker类 使后台任务的实现不那么繁琐。SwingWorker<T,V> T返回的类型,V中间数据的类型(如进度数据)。
- 由于SwingWorker<T,V>实现了 Future<T>。所以结果可以通过get方法获得(阻塞)。(一般在done方法调用get方法)。通过cancel方法取消工作。
- 每当要在工作器线程做一些工作时,构建一个新的工作器(每一个工作器只能使用一次),然后调用execute方法。
- 覆盖doInBackGround方法来完成耗时的工作
- 不时地调用publish方法来报告工作进度
- 工作线程对publish的调用 会导致在事件分配线程上的 process方法 的调用。几个对publish的调用结果,可用对 process的一次调用成批处理。process方法接收一个包含所有中间结果的列表<V>。
- 当工作完成时,done方法在事件分配线程被调用以便完成 UI的更新。
- 示例程序:
package swingworker;
import javax.swing.JFrame;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.*;
import java.io.*;
import javax.swing.filechooser.*;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
public class SwingWorkerFrame extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(()->{
SwingWorkerFrame frame = new SwingWorkerFrame();
frame.setTitle("SwingWorker Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
private static final int DEFAULT_ROWS = 20;
private static final int DEFAULT_COLUMNS = 60;
private JTextArea text;
private JLabel statusLabel;
private JFileChooser chooser;
private JMenuItem openItem;
private JMenuItem cancelItem;
private TextReader textReader;
public SwingWorkerFrame() {
chooser = new JFileChooser();
chooser.setCurrentDirectory(new File("."));
FileNameExtensionFilter filter = new FileNameExtensionFilter("Txt files", "txt");
chooser.setFileFilter(filter);
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu fileMenu = new JMenu("File");
menuBar.add(fileMenu);
openItem = new JMenuItem("Open");
openItem.addActionListener(event->{
int result = chooser.showOpenDialog(null);
if( result == JFileChooser.APPROVE_OPTION ) {
text.setText("");
openItem.setEnabled(false);
textReader = new TextReader( chooser.getSelectedFile() );
textReader.execute();
cancelItem.setEnabled(true);
}
});
cancelItem = new JMenuItem("Cancel");
cancelItem.addActionListener(event->{
textReader.cancel(true);
});
cancelItem.setEnabled(false);
fileMenu.add(openItem);
fileMenu.add(cancelItem);
text = new JTextArea(DEFAULT_ROWS, DEFAULT_COLUMNS);
text.setText("");
JScrollPane scroll = new JScrollPane(text);
add(scroll, BorderLayout.CENTER);
statusLabel = new JLabel();
statusLabel.setText("");
add(statusLabel, BorderLayout.SOUTH);
pack();
}
private class ProcessData{
public int lineNum;
public String line;
public ProcessData(int lineNum, String line ) {
this.lineNum = lineNum;
this.line = line;
}
}
private class TextReader extends SwingWorker<StringBuilder,ProcessData>{
private File directory;
private StringBuilder sb = new StringBuilder();
public TextReader(File file) {
this.directory = file;
}
@Override
public StringBuilder doInBackground() throws IOException, InterruptedException{
int lineNum = 0;
try(Scanner in = new Scanner(directory, "UTF-8")){
while(in.hasNextLine()) {
lineNum++;
String line = in.nextLine();
ProcessData data = new ProcessData(lineNum, line);
publish(data);
sb.append(line).append("\n");
Thread.sleep(1); //为了更好地显示加载过程,否则一下子就加载完成了
}
}
return sb;
}
@Override
public void process(List<ProcessData> dataList) {
if( isCancelled() ) return;
StringBuilder b = new StringBuilder();
for( ProcessData data : dataList ) {
b.append(data.line).append("\n");
}
statusLabel.setText( dataList.get(dataList.size() - 1).lineNum + "");
text.append(b.toString());
}
@Override
public void done() {
try {
StringBuilder result = get();
text.setText(result.toString());
statusLabel.setText("Done");
}catch(InterruptedException e) {
}catch(ExecutionException e) {
statusLabel.setText( "" + e.getCause() );
}catch(CancellationException e) {
text.setText("");
statusLabel.setText("Cancelled");
}
cancelItem.setEnabled(false);
openItem.setEnabled(true);
}
}
}
- 单一线程规则
- 例外情况
- 可在任一个线程里添加或移除事件监听器。当然事件监听器的方法会在事件分配线程中被触发。
- 只有很少的Swing方法时线程安全的(列举常用的)
- JTextComponent.setText
- JTextArea.insert
- JTextArea.append
- JTextArea.replaceRange
- JComponent.repaint
- JComponent.revalidate
- 例外情况