Executor框架
Execurors,扮演线程工厂的角色,通过Executors可以创建特定功能的线程池。
Executors创建线程池方法:
- newFixedThreadPool()方法,该方法返回一个固定数量的线程池,该方法的线程数始终不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中等待有空闲的线程去执行。
- newSingleThreadExecutor()方法,创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中。
- newCachedThreadPool()方法,返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若用空闲的线程则执行任务,若无任务则不创建线程,并且每一个空闲线程会在60秒后自动回收。
- newScheduledThreadPool()方法,该方法返回一个ScheduedExecurorService对象,但该线程池可以指定线程的数量。
本质都是创建ThreadPoolExecutor,通过参数进行控制,满足不同的需求。
若Executors工厂类无法满足我们的需求,可以自定义线程池,Executors工厂类里创建线程方法内部都是用了ThreadPoolExecutor这个类,这个类可以自定义线程。构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){...}
这个构造方法对于队列是什么类型的比较关键。
在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,若大于corePoolSize,则会将任务加入队列,若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,若线程数大于maximumPoolSize,则执行拒绝策略。或其他自定义方式。
无界的任务队列时:LinkedBlockingQueue,与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。当有新任务到来,系统的线程数小于corePoolSize时,则新建线程执行任务。当达到corePoolSize后,就不会继续增加。若后续仍有新的任务加入,而有又没有空闲的线程资源,则任务直接进行队列等待。若任务创建赫尔处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。
public class MyTask implements Runnable{
private int id;
private String name;
public MyTask(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public void run() {
System.out.println("run:" + this.id);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class UseThreadPoolExecutor {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, //coreSize
2, //maxSize
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3));
MyTask mt1 = new MyTask(1,"任务1");
MyTask mt2 = new MyTask(2,"任务2");
MyTask mt3 = new MyTask(3,"任务3");
MyTask mt4 = new MyTask(4,"任务4");
MyTask mt5 = new MyTask(5,"任务5");
MyTask mt6 = new MyTask(6,"任务6");
pool.execute(mt1);
pool.execute(mt2);
pool.execute(mt3);
pool.execute(mt4);
pool.execute(mt5);
pool.execute(mt6);
pool.shutdown();
}
}
public class UseThreadPoolExecutor2 implements Runnable{
private static AtomicInteger count = new AtomicInteger();
@Override
public void run() {
int num = count.incrementAndGet();
System.out.println("任务" + num);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
// BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();//无界队列
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);//有界队列
ExecutorService executors = new ThreadPoolExecutor(
5,
10,
120L,
TimeUnit.SECONDS,
queue);
for (int i = 0; i < 20; i++) {
executors.execute(new UseThreadPoolExecutor2());
}
Thread.sleep(1000);
System.out.println("队列数量:" + queue.size());
Thread.sleep(2000);
}
}
JDK拒绝策略:
- AbortPolicy:直接抛出异常组织系统正常工作。
- CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
- DiscardOidestPolicy:丢弃最老的一个请求,尝试再次提交当前任务。
- DiscardPolicy:丢弃无法处理的任务,不给予任务处理。
- 如果需要自定义拒绝策略可以实现RejectedExecutionHandler接口。一般拒绝策略记录日志,后续对日志解析处理。
/**
* 自定义拒绝策略
*/
public class MyRejected implements RejectedExecutionHandler{
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("自定义处理。。");
System.out.println("当前被拒绝任务" + r.toString());
}
}
public class UseThreadPoolExecutor {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, //coreSize
2, //maxSize
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3),
new MyRejected());
MyTask mt1 = new MyTask(1,"任务1");
MyTask mt2 = new MyTask(2,"任务2");
MyTask mt3 = new MyTask(3,"任务3");
MyTask mt4 = new MyTask(4,"任务4");
MyTask mt5 = new MyTask(5,"任务5");
MyTask mt6 = new MyTask(6,"任务6");
pool.execute(mt1);
pool.execute(mt2);
pool.execute(mt3);
pool.execute(mt4);
pool.execute(mt5);
pool.execute(mt6);
pool.shutdown();
}
}
Concurrent工具类
CountDownLacth:经常用于监听某些初始化操作,等初始化执行完毕后,通知主线程继续工作。
public class UseCountDownLatch { public static void main(String[] args) { final CountDownLatch countDownLatch = new CountDownLatch(2); Thread t1 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("t1开始进行。。"); countDownLatch.await(); System.out.println("t1继续进行。。"); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("t2进行初始化。。"); Thread.sleep(3000); countDownLatch.countDown(); System.out.println("t2初始化完成。。"); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("t3进行初始化。。"); Thread.sleep(4000); countDownLatch.countDown(); System.out.println("t3初始化完成。。"); } catch (InterruptedException e) { e.printStackTrace(); } } }); //t2、t3初始化完成后,t1继续进行 t1.start(); t2.start(); t3.start(); } }
CyclicBarrier:所有线程都准备好就都同时运行,只要没一个线程没准备好,就等待。
public class UseCyclicBarrier { static class Runner implements Runnable{ private CyclicBarrier barrier ; private String name; public Runner(CyclicBarrier barrier, String name) { this.barrier = barrier; this.name = name; } @Override public void run() { try { Thread.sleep(1000 * (new Random().nextInt(5))); System.out.println(name + "准备就绪!"); barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println(name + "run!"); } } public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(3); ExecutorService executors = Executors.newFixedThreadPool(3); //只要最后一个线程准备就绪,同时进行run executors.submit(new Runner(barrier, "张三")); executors.submit(new Runner(barrier, "李四")); executors.submit(new Runner(barrier, "王五")); executors.shutdown(); } }
Future:非常适合在处理很耗时很长的业务逻辑时使用,可以有效的减少系统的响应时间,提高系统的吞吐量。
public class UseFuture implements Callable<String>{ private String queryStr; public UseFuture(String queryStr) { this.queryStr = queryStr; } /** * 真实的业务逻辑 */ @Override public String call() throws Exception { //模拟业务处理耗时 Thread.sleep(1000); String result = queryStr + ",处理完成"; return result; } public static void main(String[] args) throws Exception { String queryStr = "query"; //构造FutureTask,并且传入需要真正进行业务逻辑处理的类,该类实现了Callable接口的类 FutureTask<String> future1 = new FutureTask<>(new UseFuture(queryStr)); FutureTask<String> future2 = new FutureTask<>(new UseFuture(queryStr)); //构建固定线程的线程池 ExecutorService executorService = Executors.newFixedThreadPool(2); Future<?> f1 = executorService.submit(future1); Future<?> f2 = executorService.submit(future2); System.out.println("请求完毕!"); try { System.out.println("主程序执行其他业务。。"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //调用获取数据方法,如果call()没有执行完成,依然进行等待 System.out.println("数据结果:" + future1.get()); System.out.println("数据结果:" + future2.get()); System.out.println("主线程继续。。"); executorService.shutdown(); } }
Semaphore:信号量非常适合高并发访问,在新系统在上线之前,对系统的访问量进行评估。
相关概念:- PV(page view)网站的总访问量,页面浏览量或点击量,用户每刷新一次会被记录一次。
- UV(unique visitor)访问网站的一台电脑客户端为一个访客,一般24小时之内相同ip的客户端只记录一次。
- QPS(query per second)每秒查询数,qps很大程度上代表了系统业务上的繁忙程度,每次请求的请求的背后,可能对应着多次磁盘I/O,多次网络请求,多个cpu时间片等。我们通过qps可以非常直观的了解当前系统情况,一旦当前qps超过所设定的预警阈值,可以可以考虑对集群进行扩容,以免压力过大导致宕机。
- RT(response time)即请求的响应时间,非常关键,直接说明前端用户的体验。
- 峰值qps=(总PV * 80%)/(60 * 60 * 24 * 24%),实际要要参考业务情况
public class UseSemaphore { public static void main(String[] args) { //线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //控制只能5个线程同时访问 final Semaphore semaphore = new Semaphore(5); //模拟20个客户端访问 for (int i = 0; i < 20; i++) { final int NO = i; Runnable run = new Runnable() { public void run() { try { //获取许可 semaphore.acquire(); System.out.println(NO + "号正在访问!"); //模拟业务处理 Thread.sleep((long) (Math.random()*10000)); //释放权限 semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }; executorService.submit(run); } try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } executorService.shutdown(); } }
锁
Lock对象,主要有重入锁和读写锁等,比synchronized更为强大,有嗅探锁定、多路分支等功能。
ReentrantLock(重入锁),在需要进行同步的代码部分加上锁定,最后一定要释放,不然其他锁无法进入。
public class UseReentrantLock {
private Lock lock = new ReentrantLock();
public void method1(){
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1..");
Thread.sleep(1000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1..");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
public void method2(){
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2..");
Thread.sleep(1000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2..");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
public static void main(String[] args) {
final UseReentrantLock rl = new UseReentrantLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
rl.method1();
rl.method2();
}
},"t1");
t1.start();
}
}
锁的等待和通知:
synchronized,多线程间进行协作工作需要Object的wait()和notify()、notifyAll()方法进行配合工作。
使用Lock的时候,可以使用Condition,这个Condition是针对具体某一把锁,只有在锁的基础上才会产生Condition。
公平锁和非公平锁:Lock lock = new ReentrantLock(booleanisFair);
lock用法:
tryLock():尝试获得锁,获得结果用true/false返回。
tryLock():在给定的时间内尝试获得锁,获得结果用true/false返回。
isFair():是否是公平锁。
isLocked():是否锁定。
getHoldCount():查询当前线程保持此锁的个数,也就是调用lock()的次数。
lockInterruptibly():优先响应中断的锁。
getQueueLength():返回正在等待获取此锁定的线程数。
getWaitQueueLength():返回等待与锁定相关的给定条件Condition的线程数。
hasQueuedThread(Thread thread):查询指定的线程是否正在等待此锁。
hasQueuedThreads():查询是否有线程正在等待此锁。
hasWaiters():查询是否有线程正在等待与此锁有关的condition条件。
ReentrantReadWriteLock(读写锁),核心是实现读写分离的锁,在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁。其本质是分成两个锁,即读锁、写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个的顺序访问。
总结:读读共享,写写互斥,读写互斥。
public class UseReentrantReadWriteLock {
//读写锁
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//读锁
private ReadLock readLock = lock.readLock();
//写锁
private WriteLock writeLock = lock.writeLock();
public void read(){
try {
readLock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
public void write(){
try {
writeLock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
final UseReentrantReadWriteLock lock = new UseReentrantReadWriteLock();
Thread t1 = new Thread(new Runnable() {
public void run() {
lock.read();
}
},"t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
lock.read();
}
},"t2");
Thread t3 = new Thread(new Runnable() {
public void run() {
lock.write();
}
},"t3");
// t1.start();
// t2.start();
t1.start();
t3.start();
}
}