并发——线程池的使用

线程池是并发问题绕不开的,这辈子都绕不开,只能学会使用才能勉强运行这样子…

基本方法

  • Executors.newFixedThreadPool
    一个固定线程数量的线程池
  • Executors.newSingleThreadExecutor
    一个光棍线程池
  • Executors.newCachedThreadPool
    一个无上限的线程池
  • Executors.newScheduledThreadPool
    一个可延时可定时的线程池

以上4种是最常见的线程池,其实在Executors中还有一些看着眼生的家伙:

  • Executors.newSingleThreadScheduledExecutor
    一个可延时可定时的光棍线程池,组合的
  • Executors.newWorkStealingPool
    一个可设置并行数量的线程池,新来的
  • Executors.unconfigurableExecutorService
    一个负责包装其他线程池的线程池,包装后不可修改
  • Executors.unconfigurableScheduledExecutorService
    一个负责包装延时定时线程池的线程池,包装后不可修改

如果只需要简单的调用,不考虑更多更细的性能问题,那么知道有一个Executors类能提供一些常用的线程池就已经够用了,否则,就需要一些更深入的了解。

调用示例

  • newFixedThreadPool
public class ThreadPoolDemo {

	public static void main(String[] args) {
		//创建多个任务
		int n = 10;
        Runnable[] tasks = new Runnable[n];
		for (int i = 0; i < n; i++) {
			final String taskExecutionFlag = "task" + (i + 1);
            tasks[i] = new Runnable() {
				@Override
				public void run() {
                  System.out.println(Thread.currentThread().getName() + "," + taskExecutionFlag);
				}
			};
		}

		testFixedThreadPool(tasks);
	}

	public static void testFixedThreadPool(Runnable[] tasks) {
        ExecutorService executor = Executors.newFixedThreadPool(4);
		for (int i = 0; i < tasks.length; i++) {
           executor.execute(tasks[i]);
		}
	}
}

输出:

pool-1-thread-2,task2
pool-1-thread-3,task3
pool-1-thread-1,task1
pool-1-thread-1,task6
pool-1-thread-1,task7
pool-1-thread-1,task8
pool-1-thread-1,task9
pool-1-thread-2,task5
pool-1-thread-3,task10
pool-1-thread-4,task4

可以看到,当为newFixedThreadPool传入的参数为4时,线程池内永远只有4条线程存在,但任务的顺序是不能保证顺序执行的;尝试将tasks的数量变得较大后,在线程池正常执行情况下,线程池中线程的数量仍是固定的,这也是名为"Fixed"的由来。

  • newSingleThreadExecutor
public class ThreadPoolDemo {

	public static void main(String[] args) {
		//创建多个任务
		int n = 10;
        Runnable[] tasks = new Runnable[n];
		for (int i = 0; i < n; i++) {
			final String taskExecutionFlag = "task" + (i + 1);
            tasks[i] = new Runnable() {
				@Override
				public void run() {
                  System.out.println(Thread.currentThread().getName() + "," + taskExecutionFlag);
				}
			};
		}

//      testFixedThreadPool(tasks);
		testSingleThreadExecutor(tasks);
	}

	public static void testSingleThreadExecutor(Runnable[] tasks){
        ExecutorService executor = Executors.newSingleThreadExecutor();
		for (int i = 0; i < tasks.length; i++) {
           executor.execute(tasks[i]);
		}
	}	
}

输出:

pool-1-thread-1,task1
pool-1-thread-1,task2
pool-1-thread-1,task3
pool-1-thread-1,task4
pool-1-thread-1,task5
pool-1-thread-1,task6
pool-1-thread-1,task7
pool-1-thread-1,task8
pool-1-thread-1,task9
pool-1-thread-1,task10

单线程不必多说,线程池内只有一条线程存在,且任务可确保按顺序执行。有意思的是,官方也不承认一条线程的线程池能称为池,所以起名为SingleThreadExecutor而不是SingleThreadPool。

  • newCachedThreadPool
public class ThreadPoolDemo {

	public static void main(String[] args) {
		//创建多个任务
		int n = 10;
        Runnable[] tasks = new Runnable[n];
		for (int i = 0; i < n; i++) {
			final String taskExecutionFlag = "task" + (i + 1);
            tasks[i] = new Runnable() {
				@Override
				public void run() {
                  System.out.println(Thread.currentThread().getName() + "," + taskExecutionFlag);
				}
			};
		}

//      testFixedThreadPool(tasks);
//      testSingleThreadExecutor(tasks);
        testCachedThreadPool(tasks);
	}

	public static void testCachedThreadPool(Runnable[] tasks) {
        ExecutorService executor = Executors.newCachedThreadPool();
		for (int i = 0; i < tasks.length; i++) {
           executor.execute(tasks[i]);
		}
	}
}

输出:

pool-1-thread-1,task1
pool-1-thread-3,task3
pool-1-thread-2,task2
pool-1-thread-4,task4
pool-1-thread-6,task6
pool-1-thread-3,task7
pool-1-thread-2,task10
pool-1-thread-4,task9
pool-1-thread-6,task8
pool-1-thread-5,task5

这种线程池的特点是无上限,可重用线程,官方推荐在大量短期异步任务时使用。从输出也可以看出来,有10个任务需要执行,但线程并非财大气粗地无上限地创建10个线程来执行任务,而是复用了名为2、3、4、6的线程来执行后续的任务。

  • newScheduledThreadPool
public class ThreadPoolDemo {

	public static void main(String[] args) {
		//创建多个任务
		int n = 10;
        Runnable[] tasks = new Runnable[n];
		for (int i = 0; i < n; i++) {
			final String taskExecutionFlag = "task" + (i + 1);
            tasks[i] = new Runnable() {
				@Override
				public void run() {
                  System.out.println(Thread.currentThread().getName() + "," + taskExecutionFlag + "," + System.currentTimeMillis());
				}
			};
		}

//      testFixedThreadPool(tasks);
//      testSingleThreadExecutor(tasks);
//      testCachedThreadPool(tasks);
        testScheduledThreadPool(tasks);
	}

	public static void testScheduledThreadPool(Runnable[] tasks) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
		for (int i = 0; i < tasks.length; i++) {
            executor.schedule(tasks[i], 100, TimeUnit.MILLISECONDS);
		}
		for (int i = 0; i < tasks.length; i++) {
            executor.scheduleAtFixedRate(tasks[i], 200, 100, TimeUnit.MILLISECONDS);
		}
		for (int i = 0; i < tasks.length; i++) {
            executor.scheduleWithFixedDelay(tasks[i], 200, 100, TimeUnit.MILLISECONDS);
		}
	}
}

输出:

太多了,就不显示了

注意一下,需要返回ScheduledExecutorService,才能调用以下接口:

schedule(Runnable command, long delay, TimeUnit unit)

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

API的参数命名很能说明作用了,这里只需要注意scheduleAtFixedRate和scheduleWithFixedDelay的细微差别,scheduleAtFixedRate执行周期是以上一个任务执行结束为起点,而scheduleWithFixedDelay的执行周期是以上一个任务执行开始为起点,所以通常情况下,scheduleAtFixedRate的时间周期会因为任务执行的耗时变得略大。

  • newWorkStealingPool
public class ThreadPoolDemo {

	public static void main(String[] args) {
		//创建多个任务
		int n = 10;
        Runnable[] tasks = new Runnable[n];
		for (int i = 0; i < n; i++) {
			final String taskExecutionFlag = "task" + (i + 1);
            tasks[i] = new Runnable() {
				@Override
				public void run() {
                  System.out.println(Thread.currentThread().getName() + "," + taskExecutionFlag);
				}
			};
		}
//      testFixedThreadPool(tasks);
//      testSingleThreadExecutor(tasks);
//      testCachedThreadPool(tasks);
//      testScheduledThreadPool(tasks);
        testWorkStealingThreadPool(tasks);
	}

	public static void testWorkStealingThreadPool(Runnable[] tasks){
        ExecutorService executor = Executors.newWorkStealingPool(4);
		for (int i = 0; i < tasks.length; i++) {
            executor.execute(tasks[i]);
		}
	}
}

输出:

ForkJoinPool-1-worker-1,task1
ForkJoinPool-1-worker-1,task5
ForkJoinPool-1-worker-1,task6
ForkJoinPool-1-worker-1,task7
ForkJoinPool-1-worker-1,task8
ForkJoinPool-1-worker-1,task9
ForkJoinPool-1-worker-1,task10
ForkJoinPool-1-worker-2,task2
ForkJoinPool-1-worker-0,task4
ForkJoinPool-1-worker-3,task3

这是一个不正经的线程池,看命名就知道,workstealing,从工作中窃取资源,上面的输出算是比较正常的情况,因为它“窃取成功"了,但有时候会因为系统资源“窃取失败”,导致线程无法正常创建,以致于整个线程池被意外终止,所以…建议正常情况下不要用这种线程池。

就简单的使用来说,知道API就足够用了,多用多试错,慢慢就会熟悉了。

(示例代码中为了明了用的Thread.currentThread().getName(),如果需要准确地查看某些线程是否复用,可以使用Thread.currentThread().hashCode())

以上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于CPU多核编程——线程池开发方面的问题,我可以向您提供一些建议和思路。 线程池是一种常用的并发编程技术,其主要作用是在多核CPU的情况下,实现任务的并行执行,提高程序的性能。在实际开发中,线程池通常都采用生产者-消费者模式进行开发。 具体来说,线程池中有一个任务队列,负责存放待执行的任务。线程池内部有一些工作线程,它们会不断地从任务队列中取出任务进行执行。如果任务队列为空,那么工作线程会被挂起,等待新的任务到来。 在开发线程池时,关键是要实现线程的管理和同步。具体工作包括: 1. 配置线程池中工作线程的数量。这个数量通常要根据CPU的核心数和任务的类型来决定。如果任务是I/O密集型,那么工作线程数量应该设置得比较大;如果任务是计算密集型,那么工作线程数量可以适当减少。 2. 实现任务队列。这个队列可以采用多种数据结构来实现,比如数组、链表、堆等。对于多线程的情况,需要考虑线程之间的同步问题,防止数据竞争。 3. 实现工作线程。这些线程需要不断地从任务队列中取出任务进行执行,并且在没有任务时进入等待状态。同时,为了保证线程之间的公平性,需要采用一些调度算法,比如轮询、优先级队列等。 4. 实现任务接口。这些接口需要包括任务的执行函数以及相关的参数,以支持不同类型的任务。 总体来说,线程池的开发比较复杂,需要在多线程的情况下考虑到各种同步和调度问题。但是一旦实现成功,线程池可以大大提高程序的运行效率和响应速度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值