线程池成长之路+使用方法

1.什么是线程池?

线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。

如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

如果用生活中的列子来说明,我们可以把线程池当做一个客服团队,如果同时有1000个人打电话进行咨询,按照正常的逻辑那就是需要1000个客服接听电话,服务客户。现实往往需要考虑到很多层面的东西,比如:资源够不够,招这么多人需要费用比较多。正常的做法就是招100个人成立一个客服中心,当有电话进来后分配没有接听的客服进行服务,如果超出了100个人同时咨询的话,提示客户等待,稍后处理,等有客服空出来就可以继续服务下一个客户,这样才能达到一个资源的合理利用,实现效益的最大化。

2.线程池分类?

(1)newSingleThreadExecutor (单线程线程池)

       这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果唯一线程出现异常,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

使用方法:

        //创建单例线程池
	ExecutorService pool = Executors.newSingleThreadExecutor();
	for (int i = 0; i < 10; i++) {
	    pool.execute(new Thread() {
		public void run() {
		    System.out.println(Thread.currentThread().getName() + "\t开始运行....");
		}
	    });
	}

(2)newFixedThreadPool(固定大小线程池)

        每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

        //创建固定大小线程池
	ExecutorService pool = Executors.newFixedThreadPool(10);
	for (int i = 0; i < 10; i++) {
	    pool.execute(new Thread() {
		public void run() {
		    System.out.println(Thread.currentThread().getName() + "\t开始运行....");
		}
	    });
	}

运行结果:


(3)newCachedThreadPool(可缓存的线程池)

ExecutorService pool = Executors.newCachedThreadPool();

        如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程,当任务数增加时,此线程池又添加新线程来处理任务。使用方法同上

(4)newScheduledThreadPool(定时,周期)

        支持定时,周期执行任务 

        // 创建线程池(延迟10秒执行任务)
	ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);
	for (int i = 0; i < 10; i++) {
	    pool.schedule(new Thread() {
		public void run() {
		    System.out.println(Thread.currentThread().getName() + "\t开始运行....");
		}
	    }, 10, TimeUnit.SECONDS);
	}
	// 周期执行任务
	pool.scheduleAtFixedRate(new Thread() {
	    public void run() {
		System.out.println(Thread.currentThread().getName() + "\t开始运行....");
	    }
	}, 1, 1, TimeUnit.SECONDS);

(5)newWorkStealingPool(jdk8才有)

3.线程池使用场景?

(1)、一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。

(2)、一个固定大小的线程池,当知道并发数量时,对线程数做限制

(3)、一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。

(4)、可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。

(5)、一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。

4.execute和submit的区别?

(1)、execute适用于不需要关注返回值的场景,只需要将线程丢到线程池中去执行就可以了
(2)、submit方法适用于需要关注返回值的场景,submit方法的定义如下:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);

          其子类AbstractExecutorService实现了submit方法,可以看到无论参数是Callable还是Runnable,最终都会被封装成RunnableFuture,然后再调用execute执行。

            public Future<?> submit(Runnable task) {
	        if (task == null) throw new NullPointerException();
	        RunnableFuture<Void> ftask = newTaskFor(task, null);
	        execute(ftask);
	        return ftask;
	    }
	    /**
	     * @throws RejectedExecutionException {@inheritDoc}
	     * @throws NullPointerException       {@inheritDoc}
	     */
	    public <T> Future<T> submit(Runnable task, T result) {
	        if (task == null) throw new NullPointerException();
	        RunnableFuture<T> ftask = newTaskFor(task, result);
	        execute(ftask);
	        return ftask;
	    }
public class ThreadPool {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        Future<String> future = pool.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "Hello allen";
            }
        });
        String result = future.get();
        System.out.println(result);
    }
}
public class ThreadPool {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        Data data = new Data();
        Future<Data> future = pool.submit(new MyRunnable(data), data);
        String result = future.get().getName();
        System.out.println(result);
    }
}
class Data {
    String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
class MyRunnable implements Runnable {
    private Data data;
    public MyRunnable(Data data) {
        this.data = data;
    }
    @Override
    public void run() {
        data.setName("yoyo");
    }
}

5.线程池的关闭?

关闭线程池可以调用shutdownNow和两个方法来实现

(1)、shutdownNow  :对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表

(2)、shutdown :当我们调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务

(3)、在有一些业务场景下,我们想要知道线程池中的任务是否全部执行完成,当我们关闭线程池之后,可以用isTerminated来判断所有的线程是否执行完成,千万不要用isShutdown,isShutdown只是返回你是否调用过shutdown的结果。




阅读更多
换一批

没有更多推荐了,返回首页