Java多线程编程---并发框架Executor

        我们都知道,在JDK1.5之前,Java中要进行业务并发时,通常需要有程序员独立完成代码实现,而当针对高质量Java多线程并发程序设计时,为防止死锁等现象的出现,比如使用java之前的wait()、notify()和synchronized等,每每需要考虑性能、死锁、公平性、资源管理以及如何避免线程安全性方面带来的危害等诸多因素,往往会采用一些较为复杂的安全策略,加重了程序员的开发负担。

        万幸的是,在 Java 5.0 提供了java.util.concurrent(简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。开发者们借助于此,将有效的减少竞争条件(race conditions)和死锁线程等。concurrent包很好的解决了这些问题,为我们提供了更实用的并发程序模型。

 

JUC包简介

        java.util.concurrent下主要的接口和类:

        1、Executor:具体Runnable任务的执行者。

      2、ExecutorService:一个线程池管理者,其实现类有多种,比如普通线程池,定时调度线程池ScheduledExecutorService等,我们能把一个Runnable,Callable提交到池中让其调度。

        3、Callable:返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。

        4、Future:是与Runnable,Callable进行交互的接口,比如一个线程执行结束后取返回的结果等等,还提供了cancel终止线程。

        5、BlockingQueue:阻塞队列。


Runnable与Callable

        public interface Runnable

        Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。

        设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。例如,Thread 类实现了 Runnable。start的意思是说某个线程已启动并且尚未停止。

        此外,Runnable 为非 Thread 子类的类提供了一种激活方式。通过实例化某个 Thread 实例并将自身作为运行目标,就可以运行实现 Runnable 的类而无需创建 Thread 的子类。大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。

        方法:void run(),使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。方法 run 的常规协定是,它可能执行任何所需的动作。


        public interface Callable

        返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。

        Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。

Executors 类包含一些从其他普通形式转换成 Callable 类的实用方法。

         方法:V call() throws Exception,计算结果,如果无法计算结果,则抛出一个异常。返回:计算的结果。

 

Future

        public interface Future

        Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明Future 形式类型、并返回 null 作为底层任务的结果。

        方法摘要

        1、boolean cancel(booleanmayInterruptIfRunning):试图取消对此任务的执行。

        2、V get():如有必要,等待计算完成,然后获取其结果。

        3、V get(longtimeout, TimeUnit unit):如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。

        4、booleanisCancelled():如果在任务正常完成前将其取消,则返回 true。

        5、booleanisDone():如果任务已完成,则返回 true。

 

Executor

        public interface Executor

        执行已提交的 Runnable 任务的对象。此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。通常使用 Executor 而不是显式地创建线程。例如,可能会使用以下方法,而不是为一组任务中的每个任务调用 newThread(new(RunnableTask())).start():

        Executorexecutor = anExecutor;

        executor.execute(newRunnableTask1());

        executor.execute(newRunnableTask2());

        ...

        不过,Executor 接口并没有严格地要求执行是异步的。在最简单的情况下,执行程序可以在调用者的线程中立即运行已提交的任务:

class DirectExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}
        更常见的是,任务是在某个不是调用者线程的线程中执行的。以下执行程序将为每个任务生成一个新线程。
class ThreadPerTaskExecutor implements Executor {
    public void execute(Runnable r) {
        new Thread(r).start();
    }
}

        许多 Executor 实现都对调度任务的方式和时间强加了某种限制。以下执行程序使任务提交与第二个执行程序保持连续,这说明了一个复合执行程序。

class SerialExecutor implements Executor {
    final Queue tasks = new ArrayDeque();
    final Executor executor;
    Runnable active;

    SerialExecutor(Executor executor) {
        this.executor = executor;
    }

    public synchronized void execute(final Runnable r) {
        tasks.offer(new Runnable() {
            public void run() {
                try {
                     r.run();
                } finally {
                     scheduleNext();
                }
             }
         });
         if (active == null) {
             scheduleNext();
         }
     }

     protected synchronized void scheduleNext() {
         if ((active = tasks.poll()) != null) {
             executor.execute(active);
         }
     }
}

        java.util.concurrent包中提供的 Executor 实现实现了ExecutorService,这是一个使用更广泛的接口。ThreadPoolExecutor 类提供一个可扩展的线程池实现。Executors 类为这些 Executor 提供了便捷的工厂方法。

        方法:void execute(Runnable command)。在未来某个时间执行给定的命令。该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定。参数:command - 可运行的任务。


ExecutorService

        public interface ExecutorService extendsExecutor

        Executor 提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。

        可以关闭 ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭ExecutorService。shutdown()方法在终止前允许执行以前提交的任务,而 shutdownNow()方法阻止等待任务启动并试图停止当前正在执行的任务。在终止时,执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。应该关闭未使用的 ExecutorService 以允许回收其资源。

    通过创建并返回一个可用于取消执行和/或等待完成的 Future,方法 submit 扩展了基本方法Executor.execute(java.lang.Runnable)。方法 invokeAny 和 invokeAll是批量执行的最常用形式,它们执行任务collection,然后等待至少一个,或全部任务完成(可使用 ExecutorCompletionService 类来编写这些方法的自定义变体)。

        Executors 类提供了用于此包中所提供的执行程序服务的工厂方法。

 

Executors

        public class Executors extends Object

        此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。此类支持以下各种方法:

        1、创建并返回设置有常用配置字符串的ExecutorService 的方法。

        2、创建并返回设置有常用配置字符串的ScheduledExecutorService 的方法。

        3、创建并返回“包装的”ExecutorService方法,它通过使特定于实现的方法不可访问来禁用重新配置。

        4、创建并返回ThreadFactory 的方法,它可将新创建的线程设置为已知的状态。

        5、创建并返回非闭包形式的Callable 的方法,这样可将其用于需要 Callable 的执行方法中。

 

创建线程池

        如果没有线程池,需要在run方法中不停判断,还有没有任务需要执行。

        线程池的通俗比喻:接待客户,为每个客户都安排一个工作人员,接待完成后该工作人员就废掉。服务器每收到一个客户请求就为其分配一个线程提供服务,服务结束后销毁线程,不断创建、销毁线程,影响性能。

        线程池:先创建多个线程放在线程池中,当有任务需要执行时,从线程池中找一个空闲线程执行任务,任务完成后,并不销毁线程,而是返回线程池,等待新的任务安排。

        线程池编程中,任务是提交给整个线程池的,并不是提交给某个具体的线程,而是由线程池从中挑选一个空闲线程来运行任务。一个线程同时只能执行一个任务,可以同时向一个线程池提交多个任务。

        1、线程池创建方法:

        a、创建一个拥有固定线程数的线程池

        ExecutorService threadPool =Executors.newFixedThreadPool(3);

        b、创建一个缓存线程池,线程池中的线程数根据任务多少自动增删,动态变化

        ExecutorService threadPool =Executors.newCacheThreadPool();

        c、创建一个只有一个线程的线程池,与单线程一样,但好处是保证池子里有一个线程,当线程意外死亡,会自动产生一个替补线程,始终有一个线程存活

        ExecutorService threadPool =Executors.newSingleThreadExector();

        2、往线程池中添加任务:

        threadPool.executor(Runnable)

        3、关闭线程池:

        threadPool.shutdown():线程全部空闲,没有任务就关闭线程池

        threadPool.shutdownNow():不管任务有没有做完,都关掉

        4、用线程池启动定时器:

        a、创建调度线程池,提交任务,延迟指定时间后执行任务

        Executors.newScheduledThreadPool(线程数).schedule(Runnable, 延迟时间,时间单位);

        b、创建调度线程池,提交任务,延迟指定时间执行任务后,间隔指定时间循环执行

        Executors.newScheduledThreadPool(线程数).schedule(Runnable, 延迟时间,间隔时间, 时间单位);

        所有的 schedule 方法都接受相对延迟和周期作为参数,而不是绝对的时间或日期。将以 Date 所表示的绝对时间转换成要求的形式很容易。例如,要安排在某个以后的 Date运行,可以使用:schedule(task,date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)。


ExecutorService生命周期

        ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行,关闭,终止。Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。如果Executor处于关闭状态,往Executor提交任务会抛出unchecked exception RejectedExecutionException。


线程池实例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

/**
 * 
 * @Description: 创建线程池的集中方法
 *
 * @author: zxt
 *
 * @time: 2018年4月8日 下午7:44:28
 *
 */
public class ThreadPoolTest {

	public static void main(String[] args) {
		// 延迟delay时间后执行
		Executors.newScheduledThreadPool(3).schedule(
				new Runnable() {
					@Override
					public void run() {
						System.out.println(Thread.currentThread().getName() + " bombing!!");
					}
				}, 
				3, 
				TimeUnit.SECONDS);
		
		// 固定频率的定时器
		Executors.newScheduledThreadPool(3).scheduleAtFixedRate(
				new Runnable() {
					@Override
					public void run() {
						System.out.println(Thread.currentThread().getName() + " bombing!!");
					}
				}, 
				4, 
				2,
				TimeUnit.SECONDS);
	}
	
	/**
	 * 
	 * @Description:固定大小的线程池
	 *
	 */
	@Test
	public void fixedThreadPoolTest() {
		// 创建一个固定大小的线程池
		ExecutorService threadPool = Executors.newFixedThreadPool(3);
		// 往线程池中添加10个任务
		for (int i = 1; i <= 10; i++) {
			final int task = i;
			threadPool.execute(new Runnable() {

				@Override
				public void run() {
					for (int j = 1; j <= 5; j++) {
						System.out.println(
								Thread.currentThread().getName() + " is looping of " + j + " for task " + task);
					}

				}
			});
		}
		
		// 当线程池中的所有线程都没有任务在执行时,关闭线程池
		threadPool.shutdown();
	}
	
	/**
	 * 
	 * @Description:可变大小的线程池
	 *
	 */
	@Test
	public void cachedThreadPoolTest() {
		// 创建一个可变大小的线程池
		ExecutorService threadPool = Executors.newCachedThreadPool();
		// 往线程池中添加10个任务
		for (int i = 1; i <= 10; i++) {
			final int task = i;
			threadPool.execute(new Runnable() {

				@Override
				public void run() {
					for (int j = 1; j <= 5; j++) {
						System.out.println(
								Thread.currentThread().getName() + " is looping of " + j + " for task " + task);
					}

				}
			});
		}
		
		// 当线程池中的所有线程都没有任务在执行时,关闭线程池
		threadPool.shutdown();
	}
	
	/**
	 * 
	 * @Description:单线程线程池(和单线程的区别在于,池中始终能保持有一个线程在)
	 *
	 */
	@Test
	public void singleThreadExecutorTest() {
		// 创建一个可变大小的线程池
		ExecutorService threadPool = Executors.newSingleThreadExecutor();
		// 往线程池中添加10个任务
		for (int i = 1; i <= 10; i++) {
			final int task = i;
			threadPool.execute(new Runnable() {

				@Override
				public void run() {
					for (int j = 1; j <= 5; j++) {
						System.out.println(
								Thread.currentThread().getName() + " is looping of " + j + " for task " + task);
					}

				}
			});
		}
		
		// 当线程池中的所有线程都没有任务在执行时,关闭线程池
		threadPool.shutdown();
	}
}

Callable与Future的应用

        获取一个线程的运行结果。

        ExecutorService threadPool =Executors.newSingleThreadExccutor();  如果不需要返回结果,就用executor方法,调用submit方法返回一个Future对象。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableAndFuture {

	public static void main(String[] args) {
		ExecutorService threadPool = Executors.newSingleThreadExecutor();
		// 接收一个Callable接口的实例对象, 返回一个Future对象
		Future future = threadPool.submit(new Callable() {
			
			// 覆盖Callable接口中的call方法,抛出异常
			@Override
			public String call() throws Exception {
				Thread.sleep(2000);
				return "hello!";
			}
		});

		System.out.println("等待结果");
		try {
			System.out.println("拿到结果:" + future.get());

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

        获取Future接收的结果:future.get();会抛出异常, future.get()没有拿到结果就会一直等待。Future取得的结果类型和Callable返回的结果类型必须一致,通过泛型实现。Callable要通过ExecutorService的submit方法提交,返回的Future对象可以取消任务。

例子:并行计算数组的和。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * 
 * @Description: 并行计算数组的和
 *
 * @author: zxt
 *
 * @time: 2018年4月8日 下午8:19:28
 *
 */
public class ConcurrentCalculator {
	
	private ExecutorService exec;
	private int cpuCoreNumber;
	private List> tasks = new ArrayList>();
	
	public ConcurrentCalculator() {
		// 得到cpu内核的个数
		cpuCoreNumber = Runtime.getRuntime().availableProcessors();
		// 有几个cpu,则创建有几个线程的线程池
		exec = Executors.newFixedThreadPool(cpuCoreNumber);
	}
	
	public void close() {
        exec.shutdown();
    }
	
	public static void main(String[] args) {
		int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 10, 11 };
		ConcurrentCalculator calc = new ConcurrentCalculator();
		Long sum = calc.sum(numbers);
		System.out.println(sum);
		calc.close();
	}

	// 内部类,实现Callable接口,将其实例提交给Executor可执行
	class SumCalculator implements Callable {
		private int[] numbers;
		private int start;
		private int end;

		public SumCalculator(final int[] numbers, int start, int end) {
			this.numbers = numbers;
			this.start = start;
			this.end = end;
		}

		public Long call() throws Exception {
			Long sum = 0l;
			for (int i = start; i < end; i++) {
				sum += numbers[i];
			}
			
			return sum;
		}
	}


	public Long sum(final int[] numbers) {
		// 根据CPU核心个数拆分任务,创建FutureTask并提交到Executor
		for (int i = 0; i < cpuCoreNumber; i++) {
			// 将数组分成多端,使用多个任务计算
			int increment = numbers.length / cpuCoreNumber + 1;
			int start = increment * i;
			int end = increment * i + increment;
			if (end > numbers.length) {
				end = numbers.length;
			}
			
			SumCalculator subCalc = new SumCalculator(numbers, start, end);
			// FutureTask实现了Future和Runable
			FutureTask task = new FutureTask(subCalc);
			tasks.add(task);
			if (!exec.isShutdown()) {
				// 因为 FutureTask 实现了 Runnable,所以可将 FutureTask 提交给 Executor 执行。
				exec.submit(task);
			}
		}
		
		return getResult();
	}

	/**
	 * 迭代每个只任务,获得部分和,相加返回
	 * 
	 * @return
	 */
	public Long getResult() {
		Long result = 0l;
		for (Future task : tasks) {
			try {
				// 如果计算未完成则阻塞  (Future中保存的是Callable的执行结果,可以使用get得到)
				Long subSum = task.get();
				result += subSum;
				
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		return result;
	}
}

CompletionService

        public interfaceCompletionService

        CompletionService用于提交一组Callable任务,其take方法返回一个已完成的Callable任务对应的Future对象。好比同时种几块麦子等待收割,收割时哪块先熟先收哪块。

        将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit 执行的任务。使用者 take 已完成的任务,并按照完成这些任务的顺序处理它们的结果。例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。

        通常,CompletionService 依赖于一个单独的Executor 来实际执行任务,在这种情况下,CompletionService 只管理一个内部完成队列。ExecutorCompletionService 类提供了此接口的一个实现。


CompletionService接口方法摘要

        1、Futurepoll():获取并移除表示下一个已完成任务的 Future,如果不存在这样的任务,则返回null。

        2、Futurepoll(long timeout, TimeUnit unit):获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则将等待指定的时间(如果有必要)。

        3Future submit(Callable task):提交要执行的值返回任务,并返回表示挂起的任务结果的 Future

        4、Futuresubmit(Runnable task, V result):提交要执行的 Runnable 任务,并返回一个表示任务完成的 Future,可以提取或轮询此任务。

        5Future take():获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。


ExecutorCompletionService方法摘要

    1、ExecutorCompletionService(Executorexecutor):使用为执行基本任务而提供的执行程序创建一个 ExecutorCompletionService,并将 LinkedBlockingQueue 作为完成队列。

        2、ExecutorCompletionService(Executor executor,BlockingQueue> completionQueue):使用为执行基本任务而提供的执行程序创建一个 ExecutorCompletionService,并将所提供的队列作为其完成队列。

示例:

// CompletionService使用实例:按照任务完成的顺序处理它们的结果
ExecutorService threadPool2 = Executors.newFixedThreadPool(10);
CompletionService completionService = new ExecutorCompletionService(threadPool2);
// 提交10个任务
for(int i = 1; i <= 10; i++) {
	final int seq = i;
	completionService.submit(new Callable() {

		@Override
		public Integer call() throws Exception {
			Thread.sleep(new Random().nextInt(5000));
				return seq;
			}
		});
	}

	// 获取任务的返回结果
	for(int i = 1; i <= 10; i++) {
		try {
			System.out.println(completionService.take().get());

		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
	}
}


例子:并行计算数组的和(改写上述方法)。

import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 
 * @Description: 并行计算数组的和
 *
 * @author: zxt
 *
 * @time: 2018年4月8日 下午8:19:28
 *
 */
public class ConcurrentCalculator2 {
	
	private ExecutorService exec;
	private int cpuCoreNumber;
	private CompletionService completionService;
	
	public ConcurrentCalculator2() {
		// 得到cpu内核的个数
		cpuCoreNumber = Runtime.getRuntime().availableProcessors();
		// 有几个cpu,则创建有几个线程的线程池
		exec = Executors.newFixedThreadPool(cpuCoreNumber);
		// 按照任务完成的顺序处理它们的结果
		completionService = new ExecutorCompletionService(exec);
	}
	
	public void close() {
        exec.shutdown();
    }
	
	public static void main(String[] args) {
		int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 10, 11 };
		ConcurrentCalculator2 calc = new ConcurrentCalculator2();
		Long sum = calc.sum(numbers);
		System.out.println(sum);
		calc.close();
	}

	// 内部类,实现Callable接口,将其实例提交给Executor可执行
	class SumCalculator implements Callable {
		private int[] numbers;
		private int start;
		private int end;

		public SumCalculator(final int[] numbers, int start, int end) {
			this.numbers = numbers;
			this.start = start;
			this.end = end;
		}

		public Long call() throws Exception {
			Long sum = 0l;
			for (int i = start; i < end; i++) {
				sum += numbers[i];
			}
			
			return sum;
		}
	}


	public Long sum(final int[] numbers) {
		// 根据CPU核心个数拆分任务,创建FutureTask并提交到Executor
		for (int i = 0; i < cpuCoreNumber; i++) {
			// 将数组分成多端,使用多个任务计算
			int increment = numbers.length / cpuCoreNumber + 1;
			int start = increment * i;
			int end = increment * i + increment;
			if (end > numbers.length) {
				end = numbers.length;
			}
			
			SumCalculator subCalc = new SumCalculator(numbers, start, end);
			if (!exec.isShutdown()) {
				// 通过CompletionService服务提交Callable任务执行
				completionService.submit(subCalc);
			}
		}
		
		return getResult();
	}

	/**
	 * 迭代每个只任务,获得部分和,相加返回
	 * 
	 * @return
	 */
	public Long getResult() {
		Long result = 0l;
		for (int i = 0; i < cpuCoreNumber; i++) {
			try {
				// 按任务完成的顺序得到返回结果
				Long subSum = completionService.take().get();
				result += subSum;
				
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		return result;
	}
}

你可能感兴趣的:(Java多线程,Java多线程—并发)