一、线程池
1.1:简单介绍
前面简单提了一嘴线程池,回想下我们前几节内容提到的线程创建:
代码块11 2
| Thread t1 = new Thread(); t1.start();
|
这段简单的代码,会在运行的时候向操作系统申请开启一个线程,当我们利用这个线程把我们的业务代码运行完,线程就会被销毁,再次会议一下线程的生命周期那张图。
但是线程是稀缺资源,当我们的程序里存在大量开启-关闭线程这种操作时,对于系统本身是一种浪费,其次,开启线程是有性能开销的,所以有没有一种方法,可以让线程创建之后,就不再死亡,而是用的时候直接去一个地方拿,用完了也不再销毁它,而是重新放回去,等待别的地方使用呢?
线程池诞生了!
跟它的名字一样,它是个用来存放线程对象的池子,最开始就会往里面放一定量的线程,别人需要使用线程的时候,它便从池子里取到一个闲置的线程用来给别人使用,别人用完了,就回收这个线程,接下来给别人用。
1.2:初始化线程池
简单记一下,有下面三种快速初始化线程池对象的方法:
- Executors.newCachedThreadPool():无限线程池。
- Executors.newFixedThreadPool(nThreads):创建固定大小的线程池。
- Executors.newSingleThreadExecutor():创建单个线程的线程池。
我们目前用newCachedThreadPool的情况比较多,它可以无限创建线程,每个线程被创建后都可以活60s的时间,期间可以被反复使用。
例子:
代码块21 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Test { private static ExecutorService threadPool = Executors.newCachedThreadPool(); public static void main(String[] args) { Thread t = new Thread(Test::async); t.start(); threadPool.execute(Test::async); } public static void async() { System.out.println("线程名为" + Thread.currentThread().getName() + "正在运行..."); } }
|
二、ReentrantLock
2.1:简介
前面讲了使用synchronized
同步块来解决多线程下操作同一块资源的安全性问题,现在,来认识juc
下同样可以实现同步控制的锁:ReentrantLock
ReentrantLock实现了Lock接口,除此之外,相比synchronized只支持非公平锁的特性,它支持灵活配置锁竞争的公平
和非公平
模式,什么是锁竞争的公平与非公平呢?我们继续来拿之前的流程图举例:
2.2:用法
代码块31 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class Word { private ReentrantLock lock = new ReentrantLock(false); private static ReentrantLock lockAll = new ReentrantLock(false); public void A() { lock.lock(); try { System.out.println("A方法被调用"); } finally { lock.unlock(); } } public void B() { lockAll.lock(); try { System.out.println("B方法被调用"); } finally { lock.unlock(); } } }
|
2.3:await、signal、signalAll
同样的ReentrantLock也支持线程通信,我们可以利用ReentrantLock来改写下前面的那个阻塞队列:
代码块41 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class BlockQueue { private List<String> msgs = new LinkedList<>(); private ReentrantLock lock = new ReentrantLock(false); private Condition condition = lock.newCondition(); public void put(String msg) { lock.lock(); try { msgs.add(msg); condition.signalAll(); } finally { lock.unlock(); } } public String get() { lock.lock(); try { if (msgs.size() == 0) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } return msgs.remove(msgs.size() - 1); } finally { lock.unlock(); } } }
|
运行效果跟之前是一样的。
三、利用线程池的Future模式做到跟join一样的效果
还是前面优化大首页的例子,我们不在将Dao层的代码列出来,我们仅看service层的异步实现,对比下不同:
代码块51 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public WebModule getWebModuleMsgSimpleAsync() throws InterruptedException { WebModule webModule = new WebModule(); Thread topTask = new Thread(() -> webModule.setTop(moduleDao.getTop())); Thread leftTask = new Thread(() -> webModule.setLeft(moduleDao.getLeft())); Thread rightTask = new Thread(() -> webModule.setRight(moduleDao.getRight())); Thread userTask = new Thread(() -> webModule.setUser(moduleDao.getUser())); topTask.start(); leftTask.start(); rightTask.start(); userTask.start(); topTask.join(); leftTask.join(); rightTask.join(); userTask.join(); return webModule; }
|
这是我们使用普通方式创建线程,然后使用join来完成的异步任务调度,结合最开始说的有关线程池的优点来看这个流程,会发现我们一下子就创建了4个线程,这是有开销的,那么线程池是否也能完成类似这种操作呢?
代码块61 2 3 4 5 6 7 8 9 10 11 12 13
| public WebModule getWebModuleMsgAsync() throws ExecutionException, InterruptedException { Future<String> top = threadPool.submit(moduleDao::getTop); Future<String> left = threadPool.submit(moduleDao::getLeft); Future<String> right = threadPool.submit(moduleDao::getRight); Future<String> user = threadPool.submit(moduleDao::getUser); WebModule webModule = new WebModule(); webModule.setTop(top.get()); webModule.setLeft(left.get()); webModule.setRight(right.get()); webModule.setUser(user.get()); return webModule; }
|
上面的代码就是我们利用线程池改造后的代码块5
,它的运行效果是和代码块5
一致的。
四、异步任务编排:CompletableFuture
有时候,我们需要拿到某个方法的结果后再去拿着这个结果去调用别的方法,这时就不能盲目使用join
或者线程池来做异步,juc提供了更高级的类:CompletableFuture
详细用法请查看附件:利用CompletableFuture优化程序的执行效率