Java多线程并发编程,多线程优化-线程池的常用创建方式及使用总结

1、简单的线程池创建Executors

1.1 newCachedThreadPool()

public static ExecutorService newCachedThreadPool()

说明:创建一个线程池。需要使用线程时从线程池中获取线程,如果无可用线程,则创建一个线程,在使用使用后放入线程池。线程池中60秒未使用的线程将被终止并从缓存中移除。

因此通过该方法创建的线程池,长时间不适用,将不怎么消耗资源。

该线程池适合用于执行短暂异步任务

代码示例:

package com.liyong.reactor.thread;

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

public class SimpleNewCachedThreadPool {
	
	static class Worker implements Runnable {
		@Override
		public void run() {
			try {
				Thread.sleep(30);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + " exe success");
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		ExecutorService executor = Executors.newCachedThreadPool();
		for (int i = 0; i < 10; i++) {
			Thread.sleep(10);
			executor.submit(new Worker());
		}
		executor.shutdown();
	}
}

 运行结果:

pool-1-thread-1 exe success
pool-1-thread-2 exe success
pool-1-thread-3 exe success
pool-1-thread-4 exe success
pool-1-thread-1 exe success
pool-1-thread-2 exe success
pool-1-thread-3 exe success
pool-1-thread-4 exe success
pool-1-thread-1 exe success
pool-1-thread-2 exe success

说明:每个任务执行30ms,每10ms提交一个任务,所以部分线程得已复用。

1.2 newFixedThreadPool(int nThreads)

public static ExecutorService newFixedThreadPool(int nThreads)

 说明:该方法创建一个固定线程数量、无界等待队列的线程池,在任何时候最多只有nThread个线程在处理任务。当所有线程都被占用时,任务将放到等待队列中,等待队列无限大。池中的线程永久有效,直到明确的关闭线程池为止。

该线程池可用于可控的任务提交和任务执行,否则在大量任务堆积时,会造成OOM,而且已经提交的任务就丢失了。

代码实例:

package com.liyong.reactor.thread;

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

public class SimpleNewFixedThreadPool {
	static class Worker implements Runnable {

		private final int index;

		public Worker(int index) {
			this.index = index;
		}

		@Override
		public void run() {
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(LocalTime.now() + " index:" + index + "  " + Thread.currentThread().getName() + " exe success");
		}
	}

	public static void main(String[] args) throws InterruptedException {
		ExecutorService executor = Executors.newFixedThreadPool(4);
		for (int i = 0; i < 10; i++) {
			Thread.sleep(10);
			System.out.println(LocalTime.now() + " thread index: " + (i + 1) + " submit success");
			executor.submit(new Worker(i + 1));
		}
		executor.shutdown();
	}
}

 运行结果:

16:54:58.823 thread index: 1 submit success
16:54:58.835 thread index: 2 submit success
16:54:58.845 thread index: 3 submit success
16:54:58.855 thread index: 4 submit success
16:54:58.865 thread index: 5 submit success
16:54:58.875 thread index: 6 submit success
16:54:58.885 thread index: 7 submit success
16:54:58.895 thread index: 8 submit success
16:54:58.905 thread index: 9 submit success
16:54:58.915 thread index: 10 submit success
16:54:59.125 index:1  pool-1-thread-1 exe success
16:54:59.135 index:2  pool-1-thread-2 exe success
16:54:59.145 index:3  pool-1-thread-3 exe success
16:54:59.155 index:4  pool-1-thread-4 exe success
16:54:59.425 index:5  pool-1-thread-1 exe success
16:54:59.435 index:6  pool-1-thread-2 exe success
16:54:59.445 index:7  pool-1-thread-3 exe success
16:54:59.455 index:8  pool-1-thread-4 exe success
16:54:59.725 index:9  pool-1-thread-1 exe success
16:54:59.735 index:10  pool-1-thread-2 exe success

说明:设置了固定的线程池大小为4,每个线程执行300ms,每隔10ms提交一个任务到线程池中。

从输出结果 ,可以看到有且仅有4个线程能执行任务,剩下的任务都在进行排队。比如 index为10的任务,16:54:58.915提交任务,16:54:59.735完成任务,总共耗时820ms,执行时间为300ms,在队列中等待了520ms的时间。

1.3 newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor()

说明:创建一个单线程,无界队列的线程池。任务保证顺序执行,随时都有且仅有一个线程在执行任务。

适合场景:适合可控任务数量,可控执行时间的任务使用,并且提供了任务的顺序执行。

代码示例:

package com.liyong.reactor.thread;

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

public class SimpleNewSingleThreadPool {
	static class Worker implements Runnable {

		private final int index;

		public Worker(int index) {
			this.index = index;
		}

		@Override
		public void run() {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(LocalTime.now() + " index:" + index + "  " + Thread.currentThread().getName() + " exe success");
		}
	}

	public static void main(String[] args) throws InterruptedException {
		ExecutorService executor = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 5; i++) {
			Thread.sleep(10);
			System.out.println(LocalTime.now() + " thread index: " + (i + 1) + " submit success");
			executor.submit(new Worker(i + 1));
		}
		executor.shutdown();
	}
}

 执行结果:

17:05:52.656 thread index: 1 submit success
17:05:52.667 thread index: 2 submit success
17:05:52.677 thread index: 3 submit success
17:05:52.687 thread index: 4 submit success
17:05:52.697 thread index: 5 submit success
17:05:52.757 index:1  pool-1-thread-1 exe success
17:05:52.857 index:2  pool-1-thread-1 exe success
17:05:52.957 index:3  pool-1-thread-1 exe success
17:05:53.057 index:4  pool-1-thread-1 exe success
17:05:53.157 index:5  pool-1-thread-1 exe success

说明: 任务执行耗时100ms,没10ms提交一个任务,总共提交5个任务,由结果的index序号观察,任务是顺序执行的,并且仅有一个线程在处理任务,其他未处理的任务再排队等待。index=5的任务,从提交任务到处理完成,总耗时460ms,等待了360ms的时间

1.4、newScheduledThreadPool(int corePoolSize)

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

 说明:创建一个线程池,可以指定线程数,排队队列无界,可以调度命令在给定的延迟之后运行,或定期执行。

代码示例:

package com.liyong.reactor.thread;

import java.io.IOException;
import java.time.LocalTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class SimpleNewScheduledThreadPool {
	static class Worker implements Runnable {

		private final int index;

		public Worker(int index) {
			this.index = index;
		}

		@Override
		public void run() {
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(LocalTime.now() + " index:" + index + "  " + Thread.currentThread().getName() + " exe success");
		}
	}

	public static void main(String[] args) throws InterruptedException, IOException {
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
		
		System.out.println(LocalTime.now() + " thread index: " + 1111 + " submit success");
		executor.schedule(new Worker(1111), 5, TimeUnit.SECONDS);
		
		System.out.println(LocalTime.now() + " thread index: " + 2222 + " submit success");
		executor.scheduleWithFixedDelay(new Worker(2222), 6000, 200, TimeUnit.MILLISECONDS);
		
//		System.out.println(LocalTime.now() + " thread index: " + 3333 + " submit success");
//		executor.scheduleAtFixedRate(new Worker(3333), 6000, 200, TimeUnit.MILLISECONDS);
		
		System.in.read();
		executor.shutdown();
	}
}

 输出结果:

17:37:23.877 thread index: 1111 submit success
17:37:23.878 thread index: 2222 submit success
17:37:29.179 index:1111  pool-1-thread-1 exe success
17:37:30.179 index:2222  pool-1-thread-2 exe success
17:37:30.680 index:2222  pool-1-thread-1 exe success
17:37:31.181 index:2222  pool-1-thread-2 exe success
17:37:31.682 index:2222  pool-1-thread-3 exe success
17:37:32.183 index:2222  pool-1-thread-1 exe success
17:37:32.684 index:2222  pool-1-thread-4 exe success
17:37:33.185 index:2222  pool-1-thread-2 exe success
17:37:33.686 index:2222  pool-1-thread-2 exe success

说明: 任务执行300ms,index 1111的任务5s后开始执行;index 2222的任务6000ms后开始执行,延时200ms后再次执行;index 3333的任务6000ms后开始执行,没间隔200ms后开始执行,间隔是以该任务上次执行结束时开始计算。

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);

指定在多久后开始执行,仅执行一次

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

指定多久后开始执行,每个delay的时长后,再次执行,从输出日志可以看到,该调度器在存在足够的线程数的情况下,该任务会存在多个线程同时在执行。

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

指定多久后开始执行,每个执行周期为period,period是从该任务上次结束后开始统计,所以即使线程数足够的清康熙,该任务只有一个线程在执行

1.5  newSingleThreadScheduledExecutor() 

public static ScheduledExecutorService newSingleThreadScheduledExecutor()

说明:该方法与1.4中的Executors.newScheduledThreadPool(1)效果一样

2、直接使用ThreadPoolExecutor类创建线程池,不使用Executors创建线程池

package com.liyong.reactor.thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CustomThreadPoolExecutor {
	public static void main(String[] args) {
		// 丢弃老的任务
		RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
		// 将抛出RejectedExecutionException异常
		new ThreadPoolExecutor.AbortPolicy();
		// 调用者自己执行任务
		new ThreadPoolExecutor.CallerRunsPolicy();
		// 丢弃任务
		new ThreadPoolExecutor.DiscardPolicy();
		/*
		 * int corePoolSize 核心线程数
		 * int maximumPoolSize 池中允许的最大线程数 
		 * long keepAliveTime 当线程数大于核心时,这个空闲线程将等待多长时间闲置后被回收
		 * TimeUnit unit 闲置时间单位
		 * BlockingQueue<Runnable> workQueue 任务队列,主要用于设置任务队列深度 
		 * RejectedExecutionHandler handler 任务拒绝策略
		 */
		new ThreadPoolExecutor(8, 64, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), handler);
	}
}

说明: 使用Executors创建的线程池,除了调度线程池外,其他线程池底层都是通过ThreadPoolExecutor来进行构造的。通过自己构造线程池,可以对线程池的各个细节进行设置。

关于拒绝策略:java提供了4中拒绝策略

        // 丢弃老的任务
        new ThreadPoolExecutor.DiscardOldestPolicy();
        // 将抛出RejectedExecutionException异常
        new ThreadPoolExecutor.AbortPolicy();
        // 调用者自己执行任务
        new ThreadPoolExecutor.CallerRunsPolicy();
        // 丢弃任务
        new ThreadPoolExecutor.DiscardPolicy();

但是这4中拒绝策略在我们真正的业务中,其实实用性不是太高。往往可以通过自定义进行优化

优化方案:

从上面的线程池配置中,线程数的配置其实是比较关键的,它直接影响了我们程序运行的效率。那怎么设置一个合适的线程池大小呢?

将被处理的任务分为: CPU密集型的、IO密集型的

如果是CPU密集型的任务,那么就是需要“长时间”占用CPU进行运算的,线程数尽量小,比如配置:CPU 个数 +1 的线程数。

如果是IO密集型的任务,因为IO操作时是不暂用CPU的,尽量不要让CPU闲下来,应尽可能大的增加线程数,比如配置: CPU 个数 * 2 +1。

Runtime.getRuntime().availableProcessors()

可以通过该方法获取当前机器的可用处理器个数。

如果任务对其他资源有依赖,比如从数据库查询数据,从远处服务器拉取数据。等待数据资源时间越长,CPU空闲的时间就越长,所以增加线程数是更好利用CPU的好方法。

因此给出一个估算的公式:

最佳线程数目 = (( 线程等待时间 + 线程 CPU 时间 ) / 线程 CPU 时间 ) * CPU 数目

 将公式进一步化简,得到:

最佳线程数目 = ( 线程等待时间与线程CPU 时间之比+1 ) * CPU 数目

因此得到结论:线程等待时间所占比例越高,需要越多线程。线程 CPU 时间所占比例越高,需要越少线程。 

自定义决绝策略:

package com.liyong.reactor.thread;

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {

	@Override
	public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
		//TODO  写日志
		//TODO  发MQ消息
		//TODO  发管理员提醒
		//TODO  executor.getQueue().put(r);  
	}

}

说明:此处只是一段伪代码,主要为了说明在自定义拒绝策略时,可以向系统管理员,以及相关负责人发出警告,这样可以快速进行人工干预,比如:加机器、代码BUG定位等相关操作。

总结:

Java 多线程开发优化有两个思路:

  • 针对锁的优化
  • 线程池优化
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值