1. 并发工具类
1.1 CountDownLatch
CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
countDown()实现计数器-1
await()等待拦截方法,等待计数器为0时再放行,否则则一直阻塞
getCount()获取当前计数器中计数数量
编写代码测试
//等待子线程全部执行完毕后在执行主线程 public static void main(String[] args) { System.out.println("主线程开始运行"); //第一个 子线程 new Thread(()->{ System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行"); }).start(); //第二个 子线程 new Thread(()->{ System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行"); }).start(); System.out.println("子线程执行完毕,主线程继续执行"); }
控制台效果
从下图可看出问题主线程先抢占了资源然后是第一个子线程,但是当第一个子线程运行完后紧接着又是主线程运行最后这才是
第二个子线程运行,并没有使所有子线程走完后再走主线程
使用CountDownLatch解决方案
编写代码测试
//等待子线程全部执行完毕后在执行主线程 //监视几个线程就写几个 当前有两个线程所以写2 private static CountDownLatch countDownLatch=new CountDownLatch(2); public static void main(String[] args) throws InterruptedException { System.out.println("主线程开始运行"); //第一个 子线程 new Thread(()->{ System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行"); //线程数量-1,通知该线程运行完毕 countDownLatch.countDown(); }).start(); //第二个 子线程 new Thread(()->{ System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行"); //线程数量-1,通知该线程运行完毕 countDownLatch.countDown(); }).start(); //等待,等待计数器中线程数为0时才能继续向下执行 countDownLatch.await(); System.out.println("子线程执行完毕,主线程继续执行"); }
控制台效果
从下图可看出是所以有子线程走完后再执行的主线程
1.2 CyclicBarrier
CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,
所有进入等待状态的线程被唤醒并继续。
CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。
CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,
所有其它线程被唤醒前被执行。
编写测试代码
//创建10个线程 for (int i=1;i<=10;i++){ new Thread(()->{ try { Thread.sleep(100); System.out.println(Thread.currentThread().getName()+"准备就绪"); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("开始比赛"); }).start(); } }
控制台效果
使用CyclicBarrier解决方案
编写测试代码
//设置等待线程数量,当线程数量到达指定数量时,统一向下运行 private static CyclicBarrier cyclicBarrier=new CyclicBarrier(10); public static void main(String[] args) { //创建10个线程 for (int i = 1; i <=10 ; i++) { new Thread(()->{ try { Thread.sleep(100); System.out.println(Thread.currentThread().getName()+"准备就绪"); //等待 cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println("开始比赛~"); }).start(); } }
控制台效果
1.3 Semaphore
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,
超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也
可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。
编写测试代码
一共有三个茅坑,每个人上完之后让下一个人上
for (int i = 1; i <= 10; i++) { new Thread(() -> { System.out.println("终于有茅坑了,可以上厕所了"); }).start(); }
控制台效果
如图是不可以的
使用Semaphore解决方案
编写测试代码
private static Semaphore semaphore=new Semaphore(3); public static void main(String[] args) { for (int i = 1; i <= 10; i++) { new Thread(() -> { if (semaphore.availablePermits()>0){ System.out.println(Thread.currentThread().getName()+"终于有茅坑了,可以上厕所了"); }else{ System.out.println(Thread.currentThread().getName()+"没有茅坑了"); } try { //申请资源 semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"前面的人终于走了~"); //模拟上厕所时间 Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+"上完了~"); //释放资源 semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } ; }).start(); } }
控制台 效果
1.4 Exchanger
可以执行线程的资源交换,线程数量必须为偶数,因为是两两相互交换资源,如果不是偶数默认情况下导致阻塞,
可以设置交换资源超时时间
编写测试代码
private static String str1="资源一"; private static String str2="资源二"; //构建资源交换 private static Exchanger<String> stringExchanger=new Exchanger<>(); public static void main(String[] args) { //第一个线程 new Thread(()->{ System.out.println(Thread.currentThread().getName()+"初始占用资源:"+str1); //资源交换,将资源交给其他线程和获取到其他线程交换过来的资源 try { String newStr=stringExchanger.exchange(str1); System.out.println(Thread.currentThread().getName()+"交换资源:"+newStr); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); //第二个线程 new Thread(()->{ System.out.println(Thread.currentThread().getName()+"初始占用资源:"+str2); //资源交换,将资源交给其他线程和获取到其他线程交换过来的资源 try { String newStr=stringExchanger.exchange(str2); System.out.println(Thread.currentThread().getName()+"交换资源:"+newStr); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); //第三个线程 new Thread(()->{ System.out.println(Thread.currentThread().getName()+"初始占用资源:"+str2); //资源交换,将资源交给其他线程和获取到其他线程交换过来的资源 try { String newStr=stringExchanger.exchange(str2,1000, TimeUnit.MILLISECONDS); System.out.println(Thread.currentThread().getName()+"交换资源:"+newStr); } catch (InterruptedException | TimeoutException e) { e.printStackTrace(); } }).start(); }
控制台效果
2. 线程池
类似于一个池子,可以存放/管理线程
2.1 使用线程池的好处
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性
2.2 线程池的作用
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时
间,从而提高效率。
如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池
中线程的开始、挂起、和中止。
2.3 线程池的分类及使用
线程池顶级类ThreadPoolExecutor最终实现Executor接口,在JUC包下,通过Executors类可以创建不同类型的线程池,
分类如下
1.newScheduledThreadPool 定时任务线程池,可以设置任务时间
//构建 一个线程池 ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 1; i <=10 ; i++) { //创建线程池 executorService.execute(()->{ System.out.println("创建线程池"+Thread.currentThread().getName()); }); }
2.newFixedThreadPool 定长线程池
//构建线程池对象 ExecutorService executorService = Executors.newFixedThreadPool(24); for (int i = 0; i < 10; i++) { //创建线程 executorService.execute(()->{ System.out.println("创建线程:"+Thread.currentThread().getName()); }); } //线程池停止 executorService.shutdown();
3.newSingleThreadExecutor 利用的是单线程,单线程处理任务,一般不用
//创建一个线程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { executorService.execute(()->{ System.out.println("创建线程:"+Thread.currentThread().getName()); }); }
4.newCachedThreadPool 带缓存的线程池
//创建一个线程池 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3); for (int i = 0; i < 10; i++) { scheduledExecutorService.schedule(()->{ System.out.println("创建线程:"+Thread.currentThread().getName()); },1000, TimeUnit.MILLISECONDS); }
3.所有的线程池分类底层调用的都是ThreadPoolExecutor()构造方法,该构造方法中每一个参数含义
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue)
3.1 corePoolSize代表核心线程池大小,当有任务时,会创建对应线程处理对应任务,当线程到达一定数量后,则会缓存到
队列当中,不会再次创建新的线程
3.2 maximumPoolSize: 线程池最大线程数,它表示在线程池中最多能创建多少个线程
3.3 keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止。
3.4 unit: 参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性
4.如何确定线程池中创建线程数量
考虑CPU密集和IO密集,如果不考虑IO情况下,一般是处理器数量+1,如果考虑IO,则处理器*2