Java线程池及相关类学习笔记(用法,相关类)

一、ThreadPoolExecutor构造方法

以参数最全的构造方法为例:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

参数详情:

参数名称类型含义
corePoolSize核心线程池容量int线程池的基本大小,也可以理解为最小容量
maximumPoolSize线程池最大容量int线程池中可以保留的最大线程容量
keepAliveTime最大空闲时间long当线程数量大于核心时,空闲线程在stop前等待新任务的最大时间,当corePoolSize=maximumPoolSize 此参数无效
unit时间单位TimeUnit时间单位,如Seconds、Milliseconds等
workQueue阻塞队列类型BlockingQueue用于保存等待中任务的阻塞队列,实现如LinkedBlockingQueue、ArrayBlockingQueue等
threadFactory线程工厂ThreadFactory如Executors.defaultThreadFactory(),Executors.privilegedThreadFactory()
handler拒绝策略RejectedExecutionHandler当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:丢弃任务并抛出异常,丢弃任务不抛异常,丢弃队列最前的任务往下执行,由调用线程出处理该任务。 默认策略为第一种。

二、六种常用线程池

2.1FixedThreadPool

以Executors提供的newFixedThreadPool(int nThreads)方法为例,该线程池有如下特点:
1.corePoolSize与maximumPoolSize相等,即所有线程均为核心线程。
2.keepAliveTime=0,该参数仅对非核心线程有效,在这里没有效果。
3.使用LinkedBlockingQueue(无界阻塞队列)实现,队列最大等待线程数为Integer.MAX_VALUE,也正因为如此,在等待队列很大时,可能在拒绝策略前产生内存溢出。
4.无序执行。
5.核心线程一旦新建永不销毁。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
            0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    }

例子:使用Executors.newFixedThreadPool(nThreads)创建一个容量为3的线程池,放入5个线程并查看线程池执行前后状态。

	public static void main(String[] args){
		ExecutorService executor=Executors.newFixedThreadPool(3);
		System.out.println("初始化后:"+executor.toString().substring(48));
		CountDownLatch executorCount=new CountDownLatch(1);
		CountDownLatch viceThreadCount=new CountDownLatch(1);
		CountDownLatch mainThreadCount=new CountDownLatch(5);
		for(int i=0;i<5;i++){
			executor.execute(new Runnable(){
				@Override
				public void run() {
					try {
						viceThreadCount.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+" hashCode:"+Thread.currentThread().hashCode());
					mainThreadCount.countDown();
				}
			});
		}
		System.out.println("所有线程准备开始执行:"+executor.toString().substring(48));
		viceThreadCount.countDown();
		try {
			mainThreadCount.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("所有线程始执行完毕:"+executor.toString().substring(48));
	}

结果:

初始化后:[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
所有线程准备开始执行:[Running, pool size = 3, active threads = 3, queued tasks = 2, completed tasks = 0]
pool-1-thread-1 hashCode:2106610656
pool-1-thread-2 hashCode:1863264879
pool-1-thread-3 hashCode:602269801
pool-1-thread-1 hashCode:2106610656
pool-1-thread-2 hashCode:1863264879
所有线程始执行完毕:[Running, pool size = 3, active threads = 0, queued tasks = 0, completed tasks = 5]

2.2CacheThreadPool

以Executors提供的newCacheThreadPool()方法为例,该线程池有如下特点:
1.corePoolSize为0,无核心线程,maximumPoolSize为2的31次方-1。
2.keepAliveTime=60秒,所有线程执行完毕后60秒内如果没有被复用将会被销毁。
3.虽然CacheThreadPool使用SynchronousQueue实现,由于maximumPoolSize为Integer.MAX_VALUE,即使SynchronousQueue没有存放元素的能力,线程也几乎不会阻塞。
4.有序执行。

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
            60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    }

例子:使用Executors.newCachedThreadPool()创建一个不限容量的线程池,放入1024个线程并查看线程池执行前后状态。

	public static void main(String[] args) throws InterruptedException {
		ExecutorService pool=Executors.newCachedThreadPool();
		CountDownLatch mainThreadCount=new CountDownLatch(1<<10);
		CountDownLatch viceThreadCount=new CountDownLatch(1);
		System.out.println("初始化后:"+pool.toString().substring(48));
		for(int i=0;i<1<<10;i++){
			pool.execute(new Runnable() {
				@Override
				public void run() {
					mainThreadCount.countDown();
					try {
						viceThreadCount.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		}
		mainThreadCount.await();
		System.out.println("执行完后:"+pool.toString().substring(48));
		viceThreadCount.countDown();
		TimeUnit.SECONDS.sleep(61l);
		System.out.println("执行完61秒时:"+pool.toString().substring(48));
	}

结果:

初始化后:[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
执行完后:[Running, pool size = 1024, active threads = 1024, queued tasks = 0, completed tasks = 0]
执行完61秒时:[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 1024]

2.3SingleThreadExecutor

由于SingleThreadExecutor实际上是通过FinalizableDelegatedExecutorService包装的newFixedThreadPool(1),下面主要说一下他与newFixedThreadPool(1)异同:
1.SingleThreadExecutor顺序执行,先进先出,而FixedThreadPool(1)无序。
2.两者在线程执行异常时都会重新创建一个线程替换之前的线程执行。
3.FixedThreadPool(1)可以被强转为ThreadPoolExecutor,而SingleThreadExecutor不能,意味着可扩展性差。
4.SingleThreadExecutor多一个finalize()方法,里面调用的是shutdown()。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

2.4ScheduledExecutorService

Executors.newScheduledThreadPool(int corePoolSize)用来创建延期执行或定期执行调度命令的线程池。特征如下:
1.自定义核心线程数,调度任务与线程并非1对1的关系,1个线程可能执行多个调度命令,也可能1个调度命令被多个线程执行。
2.除非设置了allowCoreThreadTimeOut,否则核心线程一旦新建永不销毁。
3.使用DelayedWorkQueue实现,类似DelayQueue和PriorityQueue。

	public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

常用API如下:

/**
 * 适合做从某刻开始,间隔某时间执行的定时任务,即使线程报错,睡眠或者阻塞了到点了依然执行,有点像民航
 * @param command:线程主体
 * @param initialDelay:延迟时间,调度任务经过initialDelay的时间后才会开始执行
 * @param period:执行间隔,每经过period的时间会再次调度任务
 * @param uint:时间单位,如TimeUnit.SECONDS,TimeUnit.MILLISECONDS等
 */
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                   long initialDelay, long period, TimeUnit unit);
/**
 * 参数用法同上,不一样的是这是私人飞机,如果调度执行延误了会等待执行完毕后再开始计算delay。
 */                   
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                   long initialDelay, long delay, TimeUnit unit);

2.5ForkJoinPool

ForkJoinPool是JDK1.7以后,为了解决CPU负载不均衡的问题而产生的,他可以将某个较大的任务拆分成多个子任务执行,RecursiveAction和RecursiveTask是他的两个子类,前者无返回结果,后者有返回结果,fork()方法可以让空闲线程执行拆分出去的子任务,join()方法可以汇总子任务的执行结果。
例:分别用RecursiveAction和RecursiveTask对1+2+3+…+1000000进行求和,并拆分成10个子任务执行。

public class ForkJoinPoolTest {
	static int size=1000000;
	static int[] nums= new int[size];
	//由于RecursiveAction无返回结果,使用静态的int类型去汇总总和
	static int sum=0;
	static {
		for(int i=0; i<size; i++) {
			nums[i] =i;
		}
	}
	static int add(int begin,int end,int[] arr){
		int sum=0;
		for(int i=begin; i<end; i++) {
			sum+=arr[i];
		}
		return sum;
	}
	static class TaskA extends RecursiveAction{
		int begin,end;
		public TaskA(int begin,int end){
			this.begin=begin;
			this.end=end;
		}
		@Override
		protected void compute() {
			if(end-begin<size/10){
				int middle=begin+(end-begin)/2;
				TaskA t1=new TaskA(begin,middle);
				TaskA t2=new TaskA(middle,end);
				t1.fork();
				t2.fork();
			}
			sum+=ForkJoinPoolTest.add(begin, end, nums);
		}
	}
	static class TaskB extends RecursiveTask<Integer>{
		int begin,end;
		public TaskB(int begin,int end){
			this.begin=begin;
			this.end=end;
		}
		@Override
		protected Integer compute() {
			if(end-begin<size/10){
				int middle=begin+(end-begin)/2;
				TaskB t1=new TaskB(begin,middle);
				TaskB t2=new TaskB(middle,end);
				t1.fork();
				t2.fork();
				return t1.join()+t2.join();
			}
			return ForkJoinPoolTest.add(begin, end, nums);
		}
	}
	public static void main(String[] args) throws InterruptedException {
		ForkJoinPool fjA=new ForkJoinPool();
		ForkJoinPool fjB=new ForkJoinPool();
		TaskA taskA=new TaskA(0,size);
		TaskB taskB=new TaskB(0,size);
		fjA.execute(taskA);
		Thread.sleep(1000l);
		System.out.println("TaskA计算得:"+sum);
		fjB.execute(taskB);
		System.out.println("TaskB计算得:"+taskB.join());
	}
}

结果:

TaskA计算得:1783293664
TaskB计算得:1783293664

2.6WorkStealingPool

WorkStealingPool翻译成中文为工作窃取池,顾名思义,线程池内的线程在执行完毕将会抢夺池内其他线程未开始的任务,线程池大小为CPU核心数。他本体其实就是ForkJoinPool,不过WorkStealingPool中的线程都是守护线程。

public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

例子:创建100个线程打印他们的线程名称

public class WorkStealingPoolTest {
	public static void main(String[] args) throws InterruptedException, IOException {
		System.out.println(Runtime.getRuntime().availableProcessors());
		ExecutorService service = Executors.newWorkStealingPool();
		System.out.println(service);
		CountDownLatch count = new CountDownLatch(100);
		for (int i = 0; i < 100; i++) {
			service.execute(() -> {
				System.out.println(Thread.currentThread().getName());
				count.countDown();
			});
		}
		count.await();
		System.out.println(service);
		System.in.read();
	}
}

结果:

8
java.util.concurrent.ForkJoinPool@6d06d69c[Running, parallelism = 8, size = 0, active = 0, running = 0, steals = 0, tasks = 0, submissions = 0]
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-2
......
ForkJoinPool-1-worker-1
java.util.concurrent.ForkJoinPool@6d06d69c[Running, parallelism = 8, size = 8, active = 0, running = 0, steals = 100, tasks = 0, submissions = 0]

三、相关类

3.1BlockingQueue子类

3.1.1ConcurrentLinkedQueue

ConcurrentLinkedQueue是一个线程安全的链表队列容器
例子:假设10个窗口同时出售10000张票:

	private static Queue<Integer> tickets = new ConcurrentLinkedQueue<>();
	static {
		for (int i = 0; i < 10000; i++) {
			tickets.add(i);
		}
	}
	public static void main(String[] args) {
		//10个线程模拟10个窗口
		for (int i = 0; i < 10; i++) {
			new Thread(()->{
				while(true){
					Integer poll = tickets.poll();
					if(tickets.poll() == null){
						break;
					}
					System.out.println("票号:" + poll);
				}
			}).start();
		}
	}

ConcurrentLinkedQueue获取元素常用两个方法
poll():返回队列头部的元素,同时删除它。
peek():返回队列头部的元素,但不删除

3.1.2ArrayBlockingQueue、LinkedBlockingQueue

有界阻塞队列,底层分别以数组和链表的方式存储数据。添加元素的方式有三种
add(Object o):添加超过容器上限的元素时会抛出异常。
offer(Object o):添加超过容器上限的元素时返回false,否则返回true。
put(Object o):添加超过容器上限的元素时进入阻塞等待。
获取元素的方式有n

下面是一个例子:假设容器容量为10,生产者预计生产100个产品,10个消费者一有产品就竞争消费,代码如下。

//这里使用LinkedBlockingQueue与ArrayBlockingQueue效果一致
private static BlockingQueue<String> strings = new LinkedBlockingQueue<>(10);
//private static BlockingQueue<String> strings = new ArrayBlockingQueue<>(10);
	public static void main(String[] args) {
		new Thread(()->{
			for (int i = 0; i < 100; i++) {
				try {
					strings.put("商品" + i);
					System.out.println(Thread.currentThread().getName() + " put 商品" + i);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}, "producer").start();

		for (int i = 0; i < 10; i++) {
			new Thread(()->{
				for(;;){
					try {
						System.out.println(Thread.currentThread().getName() + " take " + strings.take()); 
					} catch (Exception e) {
						e.printStackTrace();
					} 
				}
			},"consumer" + i).start();
		}

	}

3.1.3DelayQueue

延迟队列,很好玩的一个容器,可以在添加元素时设置多久以后才能拿到元素,常被使用做调度任务。
例子:往容器中添加一个3秒后获取的元素

private static BlockingQueue<MyTask> tasks = new DelayQueue<>();

	static class MyTask implements Delayed{

		long runningTime;
		
		public MyTask(long rt) {
			this.runningTime = rt;
		}

		@Override
		public int compareTo(Delayed o) { return 0; }

		@Override
		public long getDelay(TimeUnit unit) {
			return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
		}
		
		public static void main(String[] args) throws InterruptedException {
			long start = System.currentTimeMillis();
			tasks.put(new MyTask(start+3000));
			System.out.println(tasks.take()+",花费时间:"+(System.currentTimeMillis()-start));
		}

	}

结果:

concurrentContainer.DelayQueueTest$MyTask@6d06d69c,花费时间:3001

3.1.4LinkedTransferQueue

相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法,消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个null元素进入等待,等到有生产者生产出元素时则直接填充进该null节点。
例子:10个消费者待消费,生产者每1秒生产1个元素

	public static void main(String[] args) throws InterruptedException {
		LinkedTransferQueue<String> strings = new LinkedTransferQueue<>();
		for(int i=0;i<10;i++){
			new Thread(()->{
				try {
					System.out.println(Thread.currentThread().getName()+" take "+strings.take());
				} catch (Exception e) {
					e.printStackTrace();
				}
			},"t"+i).start();
		}
		while(true){
			Thread.sleep(1000l);
			strings.transfer("prod");
		}
	}

结果如下:

t0 take prod
t2 take prod
t6 take prod
t7 take prod
t5 take prod
t4 take prod
t9 take prod
t3 take prod
t1 take prod
t8 take prod

3.1.5SynchronousQueue

与LinkedTransferQueue类似,不过SynchronousQueue本身没有容量的队列,每个插入操都会阻塞等待直到存在相应的移除操作。
例子:10个消费者待消费,生产者每1秒生产1个元素

public static void main(String[] args) throws InterruptedException {
		BlockingQueue<String> strings = new SynchronousQueue<>();
		for(int i=0;i<10;i++){
			new Thread(()->{
				try {
					System.out.println(Thread.currentThread().getName()+" take "+strings.take());
				} catch (Exception e) {
					e.printStackTrace();
				}
			},"t"+i).start();
		}
		while(true){
			strings.put("prod");
		}
	}

结果如下:

t6 take prod
t9 take prod
t8 take prod
t5 take prod
t7 take prod
t3 take prod
t4 take prod
t2 take prod
t1 take prod
t0 take prod

3.2CountDownLatch

作用:通过一个计数器使某个线程等待其他线程各自执行完毕后再执行。
例子:同时创建并启动2个线程,线程1每1秒向容器添加1个元素,直到添加10个结束,线程2在线程1往容器中添加第5个元素的时候自动停止。

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		//初始化锁寄存器为1
		CountDownLatch latch = new CountDownLatch(1);
		new Thread(() -> {
			System.out.println("t2 start");
			//如果容器长度不为5主动进入等待
			if (list.size() != 5) {
				try {
					latch.await();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			System.out.println("t2 end");
		}).start();
		new Thread(() -> {
			System.out.println("t1 start");
			//遍历添加10个元素
			for (int i = 0; i < 10; i++) {
				list.add("添加元素" + i);
				System.out.print("--add" + (i + 1)+"--");
				//添加第5个元素时减少锁寄存器,唤醒线程2
				if (list.size() == 5) {
					latch.countDown();
				}
			}
			System.out.println("t1 end");
		}).start();
	}

结果如下:
在这里插入图片描述

3.3CyclicBarrier

作用:通过一个计数器使所有等待的线程同时开始
例子:让三个线程同时开始同时结束

public class CyclicBarrierTest {
	public static void main(String[] args) {
		CyclicBarrier cb=new CyclicBarrier(3);
		for(int i=0;i<3;i++){
			new Thread(()->{
				System.out.println("开始");
				try {
					cb.await();
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("结束");
			}).start();;
		}
	}
}

结果:

开始
开始
开始
结束
结束
结束

3.4Semaphore

作用:通过一个计数器控制多个线程共享该计数器,每有一个线程请求资源计数器减一(调用acquire()),每有一个线程释放资源计数器加一(调用release())。
例子:

public class SemaphoreTest {
	public static void main(String[] args) {
		Semaphore semaphore=new Semaphore(3);
		for(int i=0;i<6;i++){
			new Thread(()->{
				try {
					semaphore.acquire();
					Thread.sleep(1000l);
					System.out.println(Thread.currentThread().getName()+" acquires semaphore");
				} catch (Exception e) {
					e.printStackTrace();
				}finally {
					semaphore.release();
					System.out.println(Thread.currentThread().getName()+" had released");
				}
				
			}).start();
		}
	}
}

3.5ThreadLocal本地线程

作用:可以设置当前线程局部变量的拷贝

	private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
	public static void main(String[] args) {
		new Thread(() -> {
			threadLocal.set("t1");
			System.out.println("t1 local thread:" + threadLocal.get());
		}).start();
		new Thread(() -> {
			System.out.println("t2 local thread:" + threadLocal.get());
		}).start();
	}

结果:

t1 local thread:t1
t2 local thread:null
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值