Java基础知识:并发(下)

  • 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)
    • 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值