简述Java线程池以及使用

来源:互联网 发布:c语言的预处理 编辑:程序博客网 时间:2024/05/18 20:06

创建线程的几种方式

  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()方法获取的是Callablecall()返回值
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

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

原创粉丝点击