Java线程池简述及使用

线程

定义

线程,程序执行流的最小执行单元,是进程中的实际运作单位,经常容易和进程这个概念混淆。那么,线程和进程究竟有什么区别呢?首先,进程是一个动态的过程,是一个活动的实体。简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程

线程生命周期

线程状态流转

线程池

定义

在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在java中,内存资源是极其宝贵的,所以,就有了线程池的概念。

线程池:java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。

那么,我们应该如何创建一个线程池?java中已经提供了创建线程池的一个类:Executor

而我们创建时,一般使用它的子类:ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
	int maximumPoolSize,
	long keepAliveTime,
	TimeUnit unit,
	BlockingQueue<Runnable> workQueue,
	ThreadFactory threadFactory,
	RejectedExecutionHandler handler)
执行流程

在这里插入图片描述

JDK源码(精简)

public void execute(Runnable command){
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(c) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false)
    }
    else if (!addWorker(command, false))
        reject(command);
}
RejectedExecutionHandler(拒绝策略)

当队列和线程池都满了的时候,再有新的任务到达,就必须要有一种方法来处理新的任务。Java线程池中提供了以下四种策略:

  1. AbortPolicy:直接抛出异常(默认)
  2. CallerRunsPolicy:让调用者帮助跑这个任务
  3. DiscardOldestPolicy:丢弃队列里最老的那个任务,执行当前任务
  4. DiscardPolicy:不处理,直接扔掉

例如,我们看一个例子

 	public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

这个例子就是,如果线程池和队列都满了,那就作为调用者,你就直接自己去执行r.run吧,我线程池是不管了。另外,让调用者去自己执行,也可以让调用者不要再往线程池里放任务了,有帮于减轻线程池压力。

引入问题:AbortPolicy、DiscardOldestPolicy、DiscardPolicy都是会造成任务的丢失,那CallerRunsPolicy会造成任务的丢失吗?如果线程池和队列都满了,新进来的一个任务也让调用者自己执行了,后续的任务会被如何处理?或者问:线程池丢弃策略为CallerRunsPolicy时,来了一个任务,发现当前运行的线程已经等于最大的线程数,caller线程在忙怎么办?

答案:CallerRunsPolicy这个策略不会造成任务的丢失,当线程池和队列都满了情况下,新进来的一个任务直接调用线程的run()来执行,当后续仍然有任务进来,会进行阻塞,当线程由空闲线程或调用run()的线程执行完毕,被阻塞的任务才能执行

线程池的invokeAll、submit、execute方法的区别

submit()和execute()的区别

JDK5以后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类,两者都可以被ExecutorService执行,他们的区别是:

execute(Runnable x)没有返回值,可以执行任务,但无法判断任务是否成功完成,会将异常抛出——实现Runnable接口

submit(Runnable x)返回一个future对象,可以用这个future来判断任务是否成功完成,会将异常吃掉,如果使用submit要么有get()拉取异常处理,要么自己写try catch把任务执行的逻辑包起来——实现Callable接口

invokeAll和invokeAny的区别

invokeAll触发执行任务列表,返回的结果顺序也与任务在任务列表中的顺序一致,所有线程执行完成任务后才返回结果,如果设置了超时时间,未超时完成则正常返回结果,如果超时未完成则报异常

invokeAny将第一个得到的结果作为返回值,然后立刻终止所有的线程。如果设置了超时时间,未超时完成则正常返回,如果超时未完成则报超时异常

总结:

  1. invokeAll和invokeAny会直接造成主线程阻塞(需要设置超时时间),等待所有任务执行完成后返回结果,主线程才能继续执行
  2. submit不会造成主线程阻塞,在后面执行get()方法的时候阻塞,超时时间在get里面设置
  3. execute会新开启线程执行执行任务,不会阻塞主线程,但无返回结果
submit()吃掉“异常”

submit提交的任务会被包装成FutureTask实例,异常在内部被“吃掉”了。从源码可以看到异常被set出来了,可以用get主动拉取到

public class FutureTask<V> implements RunnableFuture<V> {
    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
}

submit()操作一般用于直接进行方法操作,如submit(()->{doSomeThing;}),当然也可以用submit(new Callable的实现类)

execute()操作一般用于无需返回结果的操作,如发送消息,不会造成阻塞,用于异步

模拟示例
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolTest {
    private static ExecutorService threadPool = new ThreadPoolExecutor(
            5,
            10,
            5L,
            TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>(),
            new ThreadFactoryBuilder().setNameFormat("test-thread-%d").build()
    );

    public static void main(String[] args) throws InterruptedException {
        System.out.println("批量任务执行开始时间:"+System.currentTimeMillis());
        List<DemoThread> callableList = Lists.newArrayList();
        for (int i = 0; i < 40; i++) {
            DemoThread demoThread = new DemoThread();
            demoThread.setOrderNo(i);
            demoThread.setQueueWaitTime(System.currentTimeMillis());
            // Future<String> future = threadPool.submit(demoThread);
            // 这里调用future.get()和不调用完全是两种情况,调用get()会造成主线程阻塞
            // System.out.println(future.get());
            callableList.add(demoThread);
        }

        // 这里可以看invokeAll时,拒绝策略造成的影响,还有不设置超时时间的影响
        List<Future<String>> futureList = threadPool.invokeAll(callableList, 40, TimeUnit.SECONDS);
        System.out.println("批量任务执行结束时间:"+System.currentTimeMillis());
        threadPool.shutdown();
    }

    static class DemoThread implements Callable<String> {
        private int orderNo;

        private Long queueWaitTime;

        private Long startExeTime;

        private Long endExeTime;

        public void setOrderNo(int orderNo) {
            this.orderNo = orderNo;
        }

        public void setQueueWaitTime(Long queueWaitTime) {
            this.queueWaitTime = queueWaitTime;
        }

        @Override
        public Object call() throws Exception {
            // 休眠1~3秒
            long sleepMillis = 1000L + new Random().nextInt(2000);
            startExeTime = System.currentTimeMillis();

            Thread.sleep(sleepMillis);

            endExeTime = System.currentTimeMillis();
            System.out.println("线程名称为:" + String.format("%-16s", Thread.currentThread().getName())
                    + "线程序号为:" + String.format("%-10s", orderNo)
                    + "休眠毫秒数为:" + String.format("%-10s", sleepMillis)
                    + "等待毫秒数为:" + String.format("%-10s", TimeUnit.MILLISECONDS.toMillis(startExeTime - queueWaitTime))
                    + "执行毫秒数为:" + String.format("%-10s", TimeUnit.MILLISECONDS.toMillis(endExeTime - startExeTime))
            );
            return "线程序号为" + orderNo;
        }
    }
}

线程池实际使用

批量查询订单详情

public class ThreadPoolTest {
    
    @Resource
	private UpdateOrderRpc updateOrderRpc;
    
    private static ExecutorService orderQueryExecutor = new ThreadPoolExecutor(
            5,
            10,
            5L,
            TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>(),
            new ThreadFactoryBuilder().setNameFormat("test-thread-%d").build(),
        	new ThreadPoolExecutor.CallerRunsPolicy()
    );
    
    public List<OrderDetail> queryOrderDetailList(List<OrderInfo> infoList) {
        List<QueryOrderThread> threadList = new Array<>(infoList.size());
        for(OrderInfo orderInfo : infoList) {
            QueryOrderThread thread = new QueryOrderThread();
            thread.setQueryOrderRpc(queryOrderRpc);
            thread.setOrderInfo(orderInfo);
            threadList.add(thread);
        }
        List<OrderDetail> detailList = new ArrayList();
        try {
            List<Future<OrderDetail>> futureList = orderQueryExecutor.invokeAll(threadList);
            for(Future<UpdateOrderResult> future: futureList){
        		try {
            		if (future.get() != null){
                		resultList.add(future.get());
            		}
        		} catch(Exception e){
            		log.error(xxx);
        		}
        	}
        } catch(Exception e){
            log.error(xxx);
        }
        return detailList;
    }
}

引入问题:这个线程池创建为什么要用CallerRunsPolicy的拒绝策略?如果改成其它类型的拒绝策略会有什么问题?invokeAll在使用上有什么注意点?并且这里的队列使用了SynchronousQueue是为什么?

使用CallerRunsPolicy一是为了防止任务丢失,二是使用其他拒绝策略造成任务丢失,invokeAll在无法拿到所有的future时会一直处于阻塞的状态,如果不希望一直等待,则使用invokeAll(Collection<? extends Callable> tasks, long timeout, TimeUnit unit),这里由于是查询操作,所以对于队列也并非只能使用SynchronousQueue,也可以使用其他队列,但是队列的长度不易过长,防止任务在队列中等待超时,从而造成获取结果失败,队列可以new LinkedBlockingDeque(1)

批量提交订单状态

public class ThreadPoolTest {

	@Resource
	private UpdateOrderRpc updateOrderRpc;

    private static ExecutorService orderUpdateExecutor = new ThreadPoolExecutor(
            5,
            10,
            5L,
            TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>(),
            new ThreadFactoryBuilder().setNameFormat("test-thread-%d").build(),
        	new ThreadPoolExecutor.CallerRunsPolicy()
    );
    
    public List<UpdateOrderResult> updateOrderList(List<OrderInfo> infoList) {
    List<UpdateOrderThread> threadList = new Array<>();
    for(OrderInfo orderInfo : infoList) {
        UpdateOrderThread thread = new UpdateOrderThread();
        thread.setUpdateOrderRpc(updateOrderRpc);
        thread.setOrderInfo(orderInfo);
        threadList.add(thread);
    }
    List<UpdateOrderResult> resultList = new ArrayList();
    try {
        List<Future<UpdateOrderResult>> futureList = orderUpdateExecutor.invokeAll(threadList);
        
        for(Future<UpdateOrderResult> future: futureList){
        	try {
            	if (future.get() != null){
                	resultList.add(future.get());
            	}
        	} catch(Exception e){
            	log.error(xxx);
        	}
        }
    } catch(Exception e){
        log.error(xxx);
    }
    return resultList;
}

引入问题:这个线程池队列使用了SynchronousQueue是为什么?

使用SynchronousQueue是为了防止宕机时队列中未执行的任务丢失,如果设置的队列有长度或者无界队列,由于队列中的任务处于内存中,在重启或者宕机时,队列中的任务就会丢失掉,从而造成丢弃掉的任务得不到执行,造成结果失败或者难以定位原因

注意:特别对于使用多线程发送MQ来更新数据状态的任务,一定不要使用无界队列或有长度的队列,因为一旦宕机,队列中的任务将会丢失,从而造成这些任务未发送出MQ(因为一般正确的数据操作是先修改本地数据状态再发送MQ更新远程数据)

ElasticSearch中用于快速检索的类

<dependency>
	<groupId>org.elasticsearch</groupId>
	<artifactId>elasticsearch</artifactId>
	<version>7.0.0</version>
</dependency>

可以先利用最大线程,再放置队列中

public class EsExecutors {
	public static EsThreadPoolExecutor newScaling(String name, int min, int max, long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory, ThreadContext contextHolder) {
        ExecutorScalingQueue<Runnable> queue = new ExecutorScalingQueue<>();
        EsThreadPoolExecutor executor = new EsThreadPoolExecutor(name, min, max, keepAliveTime, unit, queue, threadFactory, new ForceQueuePolicy(), contextHolder);
        queue.executor = executor;
        return executor;
    }
	static class ExecutorScalingQueue<E> extends LinkedTransferQueue<E> {

        ThreadPoolExecutor executor;

        ExecutorScalingQueue() {
        }

        @Override
        public boolean offer(E e) {
            // first try to transfer to a waiting worker thread
            if (!tryTransfer(e)) {
                // check if there might be spare capacity in the thread
                // pool executor
                int left = executor.getMaximumPoolSize() - executor.getCorePoolSize();
                if (left > 0) {
                    // reject queuing the task to force the thread pool
                    // executor to add a worker if it can; combined
                    // with ForceQueuePolicy, this causes the thread
                    // pool to always scale up to max pool size and we
                    // only queue when there is no spare capacity
                    return false;
                } else {
                    return super.offer(e);
                }
            } else {
                return true;
            }
        }

    }

/**
     * A handler for rejected tasks that adds the specified element to this queue,
     * waiting if necessary for space to become available.
     */
    static class ForceQueuePolicy implements XRejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            try {
                // force queue policy should only be used with a scaling queue
                assert executor.getQueue() instanceof ExecutorScalingQueue;
                executor.getQueue().put(r);
            } catch (final InterruptedException e) {
                // a scaling queue never blocks so a put to it can never be interrupted
                throw new AssertionError(e);
            }
        }

        @Override
        public long rejected() {
            return 0;
        }

    }
}

该类的使用可以在org.elasticsearch.discovery.SeedHostsResolver找到

相关参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰式的美式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值