工作中优雅的创建多线程(附带源码分析)

抄作业的直接去第三
一、创建的几种方式(看看就好,项目中都用线程池,没人单独创建,这里复习一下)
1、继承Thread

public class ThreadDemo extends Thread{

    @Override
    public void run() {
        System.out.println("我继承了Thread");
    }

    public static void main(String[] args) {
        ThreadDemo eh = new ThreadDemo();
        eh.start();
    }
}

2、实现Runnable接口

public class ImRunableDemo {
    public static void main(String[] args) {
        Thread th = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我实现了Runnable接口");
            }
        });
        th.start();
        //看起来优雅 其实没啥区别
        Thread th1 = new Thread(()->{
            System.out.println("我实现了Runnable接口");
        });
        th1.start();
    }
}

优点:逻辑和数据更好的分离出来,且能处理共享资源,并且避免单继承的局限性。
3、使用Callable和FutureTask

public class CFDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //FutureTask 实现了RunnableFuture接口,  RunnableFuture继承了Runnable   相当于一个中间桥梁 把Callable传入到Thread
        FutureTask fk = new FutureTask<Integer>(
                new Callable<Integer>() {
                    @Override
                    public Integer call() throws Exception {
                        System.out.println("我FutureTask");
                        return 11;
                    }
                });
        //参数只接受Runnable
        Thread th = new Thread(fk);
        th.start();
        System.out.println(fk.get());
    }
}

优点:可以返回一个异步值给主线程,而前两个线程直接没有交互
4、线程池

public class ExecutorsDemo {
    private static ExecutorService pool = Executors.newFixedThreadPool(3);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        
        pool.execute(()->{
            System.out.println("我实现了Runnable接口");
        });

        Future submit1 = pool.submit(()->{
                System.out.println("我实现了Runnable接口");
            }
        );

        System.out.println("submit1:"+submit1.get());
        
        Future submit = pool.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("我FutureTask");
                return 11;
            }
        });

        System.out.println("submit:"+submit.get());
    }
}

execute只接收Runnable类型的参数,且无返回值
submit可以接收Runnable类型的参数和Callable类型参数,且可以又返回值。

线程池的四种快捷方式:

public class ExecutorsPlusDemo {
    //单线程线程池
    /*特点
    * 1、按照提交顺序执行
    * 2、唯一线程存活时间是无限的
    * 3、唯一线程执行时,新任务会进入内部的阻塞队列中,阻塞队列是无界的
    * */
    private static ExecutorService newSinglePool = Executors.newSingleThreadExecutor();
    //固定数量的线程池
    /*特点
     * 1、线程数量未达到固定数量,会创建新的直到达到固定数量
     * 2、达到固定数量候会保持不变,如果一个线程异常结束,也会补充一个线程
     * 3、线程达到固定数量时,新任务会进入内部的阻塞队列中,阻塞队列是无界的
     * */
    private static ExecutorService newFixedPool = Executors.newFixedThreadPool(3);
    //可缓存线程池
    /*特点
     * 1、有新任务,如果线程池中没有空闲线程,会新建一个线程
     * 2、没有线程数量限制,大小取决于操作系统或者说取决于jvm
     * 3、如果有空闲线程,则会回收线程
     *
     * 使用场景:可以削峰,适用于突发耗时较短的场景
     * */
    private static ExecutorService newCachedPool = Executors.newCachedThreadPool();
    //可调度线程池
    /*特点
     * 1、可以提供延迟和周期性的线程
     * */
    private static ScheduledExecutorService newScheduledPool = Executors.newScheduledThreadPool(3);

    public static void main(String[] args) {


        /*for (int i = 0; i<10; i++){
            newSinglePool.execute(()->{
                sleepSecond(3000);
                //System.out.println("newSinglePool"+new Date());
            });
        }

        newSinglePool.shutdown();

        for (int i = 0; i<10; i++){
            newFixedPool.execute(()->{
                sleepSecond(3000);
                //System.out.println("newFixedPool"+new Date());
            });
        }

        newFixedPool.shutdown();*/

        newScheduledPool.scheduleAtFixedRate(
                ()->{
                    System.out.println("newScheduledPool  "+new Date());
                },//线程实例
                0,//首次执行延迟
                1,//两次开始执行的最小间隔
                TimeUnit.SECONDS//时间间隔单位
        );
        sleepSecond(5000);
        newScheduledPool.shutdown();

    }

    public static void sleepSecond(int i){
        try {
            Thread.sleep(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

二、线程池的标准创建(上述的不安全,很多都是无边界的创建线程)
上述的本质上还是通过ThreadPoolExecutor来创建的。
点开源码可以看到如下:


public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)
@param corePoolSize the number of threads to keep in the pool, even
       if they are idle, unless {@code allowCoreThreadTimeOut} is set
@param maximumPoolSize the maximum number of threads to allow in the
       pool
@param keepAliveTime when the number of threads is greater than
       the core, this is the maximum time that excess idle threads
       will wait for new tasks before terminating.
@param unit the time unit for the {@code keepAliveTime} argument
@param workQueue the queue to use for holding tasks before they are
       executed.  This queue will hold only the {@code Runnable}
       tasks submitted by the {@code execute} method.
@param handler the handler to use when execution is blocked
       because the thread bounds and queue capacities are reached

翻译成人话:

@param corePoolSize  核心线程,即使线程空闲也不会回收(工作线程没达到核心线程,会优先创建新的线程,而不是使用空闲线程)
@param maximumPoolSize  线程上限
@param keepAliveTime  线程最大空闲时间 (如果超过这个时间,非核心线程会被回收,也可以设置的回收核心线程)
@param unit  时间单位
@param workQueue 阻塞队列(核心线程在忙,就会放这个队列里面,如果满了,就会创建非核心线程,这个队列满了,再创建新线程,直到总数到达线程上限,新来的会执行拒绝策略)
@param handler  拒绝策略

BlockingQueue 阻塞队列有以下几种:

//点进接口,点向下的箭头就能看到这几个实现类
ArrayBlockingQueue   //必须设置大小,超出这个大小会创建新线程 直到超过最大线程数
LinkedBlockingDeque  //吞吐大于上面的,可以设置大小,默认Interger最大值(无界队列),newSingleThreadExecutor newFixedThreadPool就是用的这个
PriorityBlockingQueue //具有优先级的队列
DelayQueue //无界阻塞延迟队列  每个元素都有过期时间,从这里获取的时候,过期的元素才会出队newScheduledThreadPool使用的这个
SynchronousQueue 同步队列 不存储元素   也就是来一个线程创建一个线程,如果没创建,就会阻塞后面的任务,直到创建成功  newCachedThreadPool

线程的钩子,挺好用

public class ThreadPoolExecutorGZDemo {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(2,4,60,TimeUnit.SECONDS
                ,new LinkedBlockingQueue<>(2)){
            @Override
            protected void terminated(){
                System.out.println("调度器终止");
            }

            @Override
            protected void beforeExecute(Thread t,Runnable target){
                System.out.println("前钩");
                super.beforeExecute(t,target);
            }
            @Override
            protected void afterExecute(Runnable target,Throwable t){
                System.out.println("后钩");
                super.afterExecute(target,t);
            }
        };

        //执行
        pool.execute(()->{
            System.out.println("线程业务");
        });

        Thread.sleep(10000);
        pool.shutdown();


    }
}

拒绝策略
拒绝的两种情况1、达到最大数量 2、线程池关闭了
策略有以下几种:

AbortPolicy  //新任务抛出异常  throw new RejectedExecutionException
DiscardPolicy //新任务直接扔掉,是第一个的安静版本
DiscardOldestPolicy //扔掉最老的那个(队列中的元素)
CallerRunsPolicy //自己执行 不使用线程池中的线程
自定义策略  继承RejectedExecutionHandler,可以自己试一下

三、优雅的创建线程池的因素
一般就三种情况:
1、io密集型任务(大部分时间在io操作,cpu空闲,因此可以多开几个,建议设置为cpu核数的两倍)
2、cpu密集型任务 (等于cpu就行)
3、混合型任务
最佳线程数 = (线程等待时间与线程cpu时间之比+1)* cpu核数
eg:cpu计算100ms db操作900 8核
则等于80

以下是贴出来的io的代码,其他的就是在线程数量上不同

public class ThreadUtilIO {
    //cpu核数
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

    //IO处理线程数
    private static final int IO_MAX = Math.max(2,CPU_COUNT*2);

    //空闲时间
    private static final int KEEP_ALIVE_SECONDS = 30;

    //有界队列size
    private static final int QUEUE_SIZE = 128;

    //懒汉式创建线程池:用于IO密集型任务
    private static class IoIntenseTargetThreadPoolLazyHolder{
        //线程池
        private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(
                IO_MAX,
                IO_MAX,
                KEEP_ALIVE_SECONDS,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(QUEUE_SIZE),//阻塞队列 先进先出
                new CustomizableThreadFactory("io")
        );

        static{
            //当线程长时间空闲,可以自行销毁
            EXECUTOR.allowCoreThreadTimeOut(true);
            //JVM关闭时的钩子函数
            Runtime.getRuntime().addShutdownHook(
                    new Thread(()->{
                        shutdownThreadPoolGracefully(EXECUTOR);
                        }, "IO")
            );
        }
    }
    //关闭线程池的方法
    public static void shutdownThreadPoolGracefully(ExecutorService threadPool){
        if(!(threadPool instanceof ExecutorService) || threadPool.isTerminated()){
            return ;
        }
        try{
            //不再接受新线程
            threadPool.shutdown();
        }catch (SecurityException e){
            return;
        }catch (NullPointerException e){
            return;
        }

        try {
            //等待60s,等待线程执行完
            if(!threadPool.awaitTermination(60,TimeUnit.SECONDS)){
                //取消正在执行的任务
                threadPool.shutdownNow();
                if(!threadPool.awaitTermination(60,TimeUnit.SECONDS)){
                    System.err.println("线程池任务未正常执行结束");
                }
            }
        } catch (InterruptedException e) {
            threadPool.shutdownNow();
        }


        try {
            if(!threadPool.isTerminated()){
                for(int i=0; i<1000; i++){
                    if(!threadPool.awaitTermination(10,TimeUnit.MILLISECONDS)){
                        break;
                    }
                    threadPool.shutdownNow();
                }
            }
        } catch (InterruptedException e) {
            threadPool.shutdownNow();
        }catch (Throwable e){
            System.out.println(e.getMessage());
        }

    }
}

四、源码分析
阅读新感悟
1、新创建线程执行完本次的任务之后,会循环从队列中去取任务,直到队列中取不到runWorker()
2、根据1来看,执行完了就结束了,那么是怎么保证非核心销毁,核心线程保留?
逻辑在getTask()中

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(recheck) && remove(command))//如果线程池不是运行状态 从队列移除任务成功 执行拒绝策略  
			reject(command);
		else if (workerCountOf(recheck) == 0) //如果在运行状态,并且目前线程数量为0 ,加一个null任务,让线程池创建线程(1、这个其实就是个安全检查,假如工作线程死,队列中的就永远执行不了了)
			addWorker(null, false);
	}
	else if (!addWorker(command, false))// 创建非核心 如果失败则拒绝
		reject(command);
}
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {    //整体就是检查一些状态   可不看 
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&            //如果线程池状态停了 才会执行这个,都是一些状态检查,可不看
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;							//工作线程已经大于等于核心或者最大线程了  则添加失败 前者本应该进队列或者拒绝  后者本应该直接拒接策略的  但是使用了添加
                if (compareAndIncrementWorkerCount(c))		//这个以及下面的那个方法  又返回到方法  break retry; 就是跳出外循环的意思 执行下面的语句
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);    //创建线程      这个方法核心方法就这一个  下面主要还是更新一些状态    接下来是执行runWorker方法
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);                      // HashSet<Worker> workers    加入到  workers 
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {  //task 不等于空 则直接执行run 执行完成之后 置空 ,再此循环就直接取队列中的任务
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;   //核心线程的默认值allowCoreThreadTimeOut为false,如果允许销毁核心线程或者当前工作线程大于核心,则为true

            if ((wc > maximumPoolSize || (timed && timedOut))   //工作线程大于最大线程数或者(需要销毁线程并且超时) 第一次来timedOut是false,第一项也不可能大于最大 所以不执行这里
                && (wc > 1 || workQueue.isEmpty())) {			//工作线程大于1 或者队列是空  
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;											//继续循环   (队列有数据不会走这里)
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :    //在这个时间内能取出任务就好,取不出,阻塞这么一段时间就返回null
                    workQueue.take();              //允许销毁  阻塞一个允许空闲时间   不允许的话取到任务执行 取不到就阻塞就阻塞在队列这里
                if (r != null)
                    return r;
                timedOut = true;				//改成true  第二次就能判断是否允许销毁的标识了
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值