线程池可以避免频繁创建销毁线程,提高性能。常见如数据库的连接池。

创建线程的几种方式

  1. 继承Thread,重写**run()**方法
  2. 实现Runnable接口,实现**run()**方法
  3. 实现Callable接口,实现**Call()**方法
  4. 使用线程池,并向其提交任务task,其内部自动创建线程调度完成。 ####上述对比: 一般来说,使用第一种和第二种比较普遍,考虑到Java不支持多继承,通常使用第二种,实现Runnable接口创建线程。 然而,假设创建线程进行一些复杂的处理,比如需要执行后做出反馈,这时候可以使用实现Callable方式创建线程。 About Callable的返回值,可以使用Future或者FutureTask来获取。

Future

它是一个接口,包含方法: boolean cancel(); 尝试取消任务,返回ture包括(任务已完成,任务成功取消) boolean isCancelled(); 区别于上一种方法这个更像是一个未完成状态(完成任务之前的取消),不包括已完成 boolean isDone(); 判断任务是否完成 get(); 获取执行结果,如果任务在执行该方法会阻塞,直到有返回值 get(long timeout,TimeUnitunit); 在有限时间内阻塞得到返回值,否则返回null

FutureTask:

底部实现RunnableFuture接口,而RunnableFuture继承了FutureRunnable 所以FutureTask同样具备上述5个方法。 参考部分源码:

/**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Callable}.
     *
     * @param  callable the callable task
     * @throws NullPointerException if the callable is null
     */
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

    /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Runnable}, and arrange that {@code get} will return the
     * given result on successful completion.
     *
     * @param runnable the runnable task
     * @param result the result to return on successful completion. If
     * you don't need a particular result, consider using
     * constructions of the form:
     * {@code Future<?> f = new FutureTask<Void>(runnable, null)}
     * @throws NullPointerException if the runnable is null
     */
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

可以看到FutureTask可以传入参数Callable或者Runnable,Result作为构造方法,区别在于:

  1. 前者调用get()方法获取的是Callable的**call()**返回值
  2. 后者传入参数RunnableResult,如果执行成功,返回Result

Futuretask和Callable实现创建线程的Demo

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableThread {
	public static void main(String[] args) {
		ThreadCall tc = new ThreadCall();
		FutureTask<String> result = new FutureTask<>(tc);
		Thread t = new Thread(result);
		t.start();
		while (true) {
			if (result.isDone()) {
				try {
					System.out.println("Call执行结束:"+result.get());
					break;
				} catch (InterruptedException | ExecutionException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

class ThreadCall implements Callable<String>{

	@Override
	public String call() throws Exception {
		return "为Java打Call!";
	}
	
}

执行结果: Call执行结束:为Java打Call!


线程池:

线程池可以避免频繁创建销毁线程,提高性能。常见如数据库的连接池。JUC(java.util.concurrent)中,Java提供了一个工具类Executors用于创建线程池[多种类型],返回一个执行器,再将Runnable或者Callable提交到执行器中[详见demo]。

线程池的几个类型:
  • newCachedThreadPool(int nThreads) 接受任务才创建线程,如果某个线程空闲超过60秒,则会被撤销。
  • newFixedThreadPool() 固定线程数量的线程池,如果线程池数量超过nThreads,新任务会放在任务队列中,如果某个线程终止了,另一个线程会来取代它。
  • newSingleThreadExecutor() 只有一个线程的线程池,依次执行任务
  • newScheduledThreadPool(int corePoolSize) 指定按照设置的时间周期执行任务,池中线程数量根据参数corePoolSize决定,即时有线程空闲状态也不会撤销。

Demo:

package com.dd.code.ThreadByCallable;

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

public class TestThreadPool {
	
	public static void main(String[] args) {
		//创建线程池(5个线程)
		ExecutorService pool = Executors.newFixedThreadPool(5);
		ThreadPoolDemo tpd = new ThreadPoolDemo();
		
		//为线程池分配10个任务
		for (int i = 0; i < 10; i++) {
			pool.submit(tpd);
		}
		//关闭线程池(等待任务执行完成才关闭),区别于shutdownnow立即关闭
		pool.shutdown();
	}
	
}

class ThreadPoolDemo implements Runnable{

	private int i = 0;
	
	@Override
	public void run() {
		System.out.println("当前线程号:"+Thread.currentThread().getName() + ",i = " + i++);
	}
	
}

执行结果:

当前线程号:pool-1-thread-3,i = 2
当前线程号:pool-1-thread-5,i = 3
当前线程号:pool-1-thread-1,i = 0
当前线程号:pool-1-thread-2,i = 1
当前线程号:pool-1-thread-3,i = 6
当前线程号:pool-1-thread-5,i = 5
当前线程号:pool-1-thread-4,i = 4
当前线程号:pool-1-thread-3,i = 8
当前线程号:pool-1-thread-2,i = 7
当前线程号:pool-1-thread-1,i = 9

可以看到,尽管分配10个任务给线程池,然而我们初始化5个固定线程的线程池,所以线程池执行的所在进程号不会超过5,而且可以看出在newFixedThreadPool中线程的调度是随机的。


线程池多任务计算

假如我们要使用线程池来执行相应计算,并且取得返回值要如何实现呢? 通常可以使用Future或者FutureTask以结合Callable来实现。

思路:

  1. 创建所需线程池,返回执行器[ExecutorService]executor
  2. 使用FutureTask构造方法传入需要计算的Callable实例化创建FutureTask
  3. 调用执行器executor的方法executor.execute(futuretask);
  4. 创建**List<FutureTask>**将执行的**futuretask**添加到List中,方便后续取值。 ####示例: **下面模拟一个计算延迟操作,我们来看看多线程和单线程执行的效率对比**
package com.dd.code.ThreadByCallable;

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

public class MutilThreadTask {
	// 线程(内部类)实现Callable接口
	static class MyCallable implements Callable<Long> {
		private long uid;

		public MyCallable(long uid) {
			super();
			this.uid = uid;
		}

		@Override
		public Long call() throws Exception {
			return Work(uid);
		}

		public long Work(long uid) {
			try {
				//模拟查询操作延迟
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return uid * 2;
		}

	}

	public static void main(String[] args) {
		// 多线程累加list
		List<Long> uidList = new ArrayList<Long>();
		for(int i = 0; i < 100; i++){
			uidList.add(10L);
		}
		
		long t1  = System.currentTimeMillis();
		//使用单线程进行计算
		System.out.println("累加结果:" + SumWithOneThread(uidList));
		System.out.println("常规计算时间:"+(System.currentTimeMillis()-t1));
		
		long t2  = System.currentTimeMillis();
		//创建10个线程的线程池参与运算
		System.out.println("累加结果:" + SumWithMutilThread(uidList,10));
		System.out.println("线程池计算时间:"+(System.currentTimeMillis()-t2));
	}
	
	static Long SumWithOneThread(List<Long> uidList){
		Long result = 0L;
		for (Long l : uidList) {			
			result += DoubleIt(l);
		}
		return result;
	}
	
	static Long DoubleIt(Long l){
		try {
			//模拟延迟计算
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return l*2;
	}
	
	static Long SumWithMutilThread(List<Long> uidList,int THREAD_NUM) {
		// 创建线程池
		ExecutorService executor = Executors.newFixedThreadPool(THREAD_NUM);
		// 关于Future和Futuretask可以参考http://blog.csdn.net/zmx729618/article/details/51596414
		// 创建任务列表
		List<FutureTask<Long>> ftlist = new ArrayList<FutureTask<Long>>();
		// MutilThreadTask mt = new MutilThreadTask();
		// 逐个执行任务并且添加到任务列表
		for (Long uid : uidList) {
			/*
			 * 实例化内部类MyCallable方法
			 * 1.把内部类设为静态. 
			 * 2.通过外部类.实例化内部类,MyCallable callable = mt.new MyCallable(uid);
			 */
			MyCallable callable = new MyCallable(uid);
			FutureTask<Long> futuretask = null;
			try {
				futuretask = new FutureTask<Long>(callable);
				executor.execute(futuretask);
			} catch (RejectedExecutionException e) {
				//Re:处理进入线程池失败的情况,也就是说,不仅获取线程资源失败,并且由于等待队列已满,甚至无法进入队列直接 失败
				e.printStackTrace();
			}
			ftlist.add(futuretask);
		}
		// 遍历任务列表,把完成任务取出来获取数据
		long totalResult = 0L; // 初始化计算数据量
		for (FutureTask<Long> ft : ftlist) {
			Long result = null;
			try {
				result = ft.get();
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
			if (result != null) {
				totalResult += result;
			}
		}
		// 关闭执行器
		executor.shutdown();
		return totalResult;
	}

}

执行结果:

累加结果:2000
常规计算时间:10035
累加结果:2000
线程池计算时间:1020

效率对比

果然,使用线程池创建多个线程计算效率快了许多,但是使用多线程就一定快吗? 我们稍作修改一下方法的参数,只给FutureTask分配5个任务[区别于之前的100个任务],并且将模拟计算时间修改为1毫秒[区别于之前的100毫秒],再看看执行结果。

累加结果:100
常规计算时间:6
累加结果:100
线程池计算时间:24

发现使用线程池的多线程计算反而更慢了,可以初步断定,如果任务量不是很大,计算量不是很复杂的情况下,多线程并不是首先,相反线程池还需要耗费资源去维护池中的线程,所以考虑多线程的使用场景,还要权衡一下任务量。