Java发令枪ConcurrentExecutor之控制线程数,并发数

前言

前段时候学习单例模式的时候,有用到多线程并发数去测试单例模式的线程安全。但是当时时间比较紧没有进行记录,今天特地记录一下。

1、先看代码

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


/**
* @author :Jarvisy
* @date :Created in 2020/9/16 1:20
* @description :
*/
public class ConcurrentExecutor {
    /**
     * @param runHandler
     * @param executeCount    发起请求总数
     * @param concurrentCount 同时并发执行的线程数
     * @throws Exception
     */
    public static void execute(final RunHandler runHandler, int executeCount, int concurrentCount) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //控制信号量,此处用于控制并发的线程数
        final Semaphore semaphore = new Semaphore(concurrentCount);
        //闭锁,可实现计数量递减
        final CountDownLatch countDownLatch = new CountDownLatch(executeCount);
        for (int i = 0; i < executeCount; i++) {
            executorService.execute(new Runnable() {
                public void run() {
                    try {
                        //执行此方法用于获取执行许可,当总计未释放的许可数不超过executeCount时,
                        //则允许同性,否则线程阻塞等待,知道获取到许可
                        semaphore.acquire();
                        runHandler.handler();//回调函数
                        //释放许可
                        semaphore.release();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();//线程阻塞,知道闭锁值为0时,阻塞才释放,继续往下执行
        executorService.shutdown();
    }


    public interface RunHandler {
        void handler();
    }
}

2、Executors

java通过Executors可以创建四种线程池:

  1. newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。超出的线程会在队列中等待
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。

第一种:newCachedThreadPool
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(index * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(index);
                }
            });
        }

第二种:newFixedThreadPool
因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  
  for (int i = 0; i < 10; i++) {  
   final int index = i;  
   fixedThreadPool.execute(new Runnable() {  
    public void run() {  
     try {  
      System.out.println(index);  
      Thread.sleep(2000);  
     } catch (InterruptedException e) {  
      e.printStackTrace();  
     }  
    }  
   });

第三种:newScheduledThreadPool

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);  
  scheduledThreadPool.scheduleAtFixedRate(new Runnable() {  
   public void run() {  
    System.out.println("delay 1 seconds, and excute every 3 seconds");  
   }  
  }, 1, 3, TimeUnit.SECONDS);

第四种:newSingleThreadExecutor

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();  
  for (int i = 0; i < 10; i++) {  
   final int index = i;  
   singleThreadExecutor.execute(new Runnable() {  
    public void run() {  
     try {  
      System.out.println(index);  
      Thread.sleep(2000);  
     } catch (InterruptedException e) {  
      e.printStackTrace();  
     }  
    }  
   });

3、ExecutorService

ExecutorService的几个方法:

1、execute(Runnable) 这个方法接收一个Runnable实例,并且异步的执行。 这个方法有个问题,就是没有办法获知task的执行结果。如果我们想获得task的执行结果,我们可以传入一个Callable的实例。

2、submit(Runnable) submit(Runnable)和execute(Runnable)区别是前者可以返回一个Future对象,通过返回的Future对象,我们可以检查提交的任务是否执行完毕。如果任务执行完成,future.get()方法会返回一个null。注意,future.get()方法会产生阻塞。

3、submit(Callable) submit(Callable)和submit(Runnable)类似,也会返回一个Future对象,但是除此之外,submit(Callable)接收的是一个Callable的实现,Callable接口中的call()方法有一个返回值,可以返回任务的执行结果,而Runnable接口中的run()方法是void的,没有返回值。如果任务执行完成,future.get()方法会返回Callable任务的执行结果。注意,future.get()方法会产生阻塞。

4、invokeAny(…) 这个方法接收的是一个Callable的集合,执行这个方法不会返回Future,但是会返回所有Callable任务中其中一个任务的执行结果。这个方法也无法保证返回的是哪个任务的执行结果,反正是其中的某一个。

5、invokeAll(…) invokeAll(…)与 invokeAny(…)类似也是接收一个Callable集合,但是前者执行之后会返回一个Future的List,其中对应着每个Callable任务执行后的Future对象。

ExecutorService的关闭:
1、shutdown() :停止接收新任务,原来的任务继续执行

  1. 停止接收新的submit的任务;
  2. 已经提交的任务(包括正在跑的和队列中等待的),会继续执行完成;
  3. 等到第2步完成后,才真正停止;

2、shutdownNow():停止接收新任务,原来的任务停止执行

  1. 跟 shutdown() 一样,先停止接收新submit的任务;
  2. 忽略队列里等待的任务;
  3. 尝试将正在执行的任务interrupt中断;
  4. 返回未执行的任务列表;

3、awaitTermination(long timeOut, TimeUnit unit):当前线程阻塞 timeout 和 TimeUnit 两个参数,用于设定超时的时间及单位
当前线程阻塞,直到:

  1. 等所有已提交的任务(包括正在跑的和队列中等待的)执行完;
  2. 或者 等超时时间到了(timeout 和 TimeUnit设定的时间);
  3. 或者 线程被中断,抛出InterruptedException;

然后会监测 ExecutorService 是否已经关闭,返回true(shutdown请求后所有任务执行完毕)或false(已超时)

4、shutdown()shutdownNow() 的区别
shutdown() 只是关闭了提交通道,用submit()是无效的;而内部该怎么跑还是怎么跑,跑完再停。
shutdownNow() 能立即停止线程池,正在跑的和正在等待的任务都停下了。

5、shutdown()awaitTermination() 的区别
shutdown() 后,不能再提交新的任务进去;但是 awaitTermination() 后,可以继续提交。
awaitTermination() 是阻塞的,返回结果是线程池是否已停止(true/false);shutdown() 不阻塞。

6、总结
1、优雅的关闭,用 shutdown()
2、想立马关闭,并得到未执行任务列表,用shutdownNow()
3、优雅的关闭,并允许关闭声明后新任务能提交,用 awaitTermination()
4、关闭功能 【从强到弱】 依次是:shuntdownNow() > shutdown() > awaitTermination()

4、Semaphore

Semaphore是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。

Semaphore 初始化必须提供并发线程数,因为Semaphore 并没有提供更新并发数量的函数。

Semaphore主要方法:
1、类Semaphore的构造函数permits 是许可的意思,代表同一时间,最多允许permits个线程执行acquire() 和release()之间的代码。
2、方法acquire() 的功能是每调用1次此方法,就消耗掉1个许可。
3、方法acquire(n) 的功能是每调用1次此方法,就消耗掉n个许可。
4、方法release() 的功能是每调用1次此方法,就动态添加1个许可。
5、方法release(n) 的功能是每调用1次此方法,就动态添加n个许可。
6、方法acquireUnnterruptibly() 作用是是等待进入acquire() 方法的线程不允许被中断。
7、方法availablePermits() 返回Semaphore对象中当前可以用的许可数。
8、方法drainPermits() 获取并返回所有的许可个数,并且将可用的许可重置为0
9、方法 getQueueLength() 的作用是取得等待的许可的线程个数
10、方法 hasQueueThreads() 的作用是判断有没有线程在等待这个许可
11、公平和非公平信号量:
有些时候获取许可的的顺序与线程启动的顺序有关,这是的信号量就要分为公平和非公平的。所谓的公平信号量是获得锁的顺序与线程启动的顺序有关,但不代表100%获得信号量,仅仅是在概率上能保证,而非公平信号量就是无关的。
例如:

Semaphore semaphore = new Semaphore(1false);

False:表示非公平信号量,即线程启动的顺序与调用semaphore.acquire() 的顺序无关,也就是线程先启动了并不代表先获得 许可。
True:公平信号量,即线程启动的顺序与调用semaphore.acquire() 的顺序有关,也就是先启动的线程优先获得许可。

12、方法tryAcquire() 的作用是尝试获取1个许可。如果获取不到则返回false,通常与if语句结合使用,其具有无阻塞的特点。无阻塞的特点可以使不至于在同步处于一直持续等待的状态。
13、方法tryAcquire(n) 的作用是尝试获取n个许可,如果获取不到则返回false
14、方法tryAcquire(long timeout,TimeUnit unit) 的作用是在指定的时间内尝试获取1个许可,如果获取不到则返回false
15、方法tryAcquire(int permits,long timeout,TimeUnit unit) 的作用是在指定的时间内尝试获取n 个许可,如果获取不到则返回false

5、 CountDownLatch

CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。

CountDownLatch 只有一个构造方法,需要传入一个int类型的参数,这个数值一般是你的线程数

CountDownLatch 类中有三个方法是最重要的:

//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
//将count值减1
public void countDown() { };  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值