1.Jdk的多任务执行框架
JDK提供了一套线程框架Executor来帮助开发者有效的进行线程控制,Executors扮演线程工厂的角色,其创建线程的方法如下
- newFixedThreadPool() 返回固定数量的线程池,该方法的线程数始终不变。若线程空闲则立即执行 否则暂缓到队列中
- newSingleThreadPool() 创建一个线程池,若线程空闲则立即执行 否则暂缓到队列中
- newCachedThreadPool() 返回一个可根据实际情况调整线程个数的线程池 ,若线程空闲则立即执行 否则暂缓到队列中
- newScheduledThreadPool() 返回一个ScheduledExecutorService对象,但该线程可以执行线程的数量(schedule 排定/安排/目录)
ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(10);
//command 就是一个Thread
ScheduledFuture<?> scheduledTask =
scheduler.scheduleWithFixedDelay(command,5,1, TimeUnit.SECONDS);
若Executors工厂类无法满足我们的需求,可以自己去创建自定义的线程池。自定义线程池的构造方法如下
public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//线程的空闲时间
TimeUnit unit,//给定单元粒度的时间段
BlockingQueue<Runnable> workQueue,//有界、无界队列
RejectedExecutoionHandler handler//任务拒绝策略
){.....}
使用什么队列对该构造方法来说比较关键
- 使用有界队列 如有任务需要执行 如果实际线程数<corePoolSize,则创建线程
如果实际线程数>corePoolSize,则将任务添加到缓存队列
如果缓存队列已满 ,总线程<maximumPoolSize, 则创建线程
总线程>maximumPoolSize, 则执行拒绝策略
- 使用无界队列LinkedBlockingQueue 除非系统资源耗尽 否则不会出现入队失败
如有任务需要执行 如果实际线程数<corePoolSize,则创建线程
如果实际线程数>corePoolSize,则将任务添加到缓存队列,
直到资源耗尽
BlockingQueue<Runnable> queue =
//new LinkedBlockingQueue<Runnable>();//无界队列
new ArrayBlockingQueue<Runnable>(10);//有界队列
ExecutorService executor = new ThreadPoolExecutor(
5, //core
10, //max
120L, //120s
TimeUnit.SECONDS,
queue);
JDK的拒绝策略
- AbortPolicy:直接抛出异常 系统正常工作
- CallerRunsPolicy:只要线程池未被关闭 尝试运行被丢弃的任务
- DiscardOldestPolicy:丢失最老的请求 尝试提交当前任务
- DiscardPolicy:丢弃无法处理的任务不给予处理
JDK提供的拒绝策略不友好,可以自定义拒绝策略,实现RejectedExecutionHandler接口(添加日志等等)
public class MyRejected implements RejectedExecutionHandler {
public MyRejected(){}
@Override
public void rejectedExecution(Runnable r,
ThreadPoolExecutor executor) {
System.out.println("自定义处理");
System.out.println("当前被拒绝的任务为"+r.toString());
}
}
2.Concurrent.util工具类详解
CyclicBarrier
假设每一个线程代表一个运动员,当运动员都准备好了,才能一起出发。
CyclicBarrier barrier = new CyclicBarrier(3);
CountDownLatch
经常用于监听某些初始化操作,当初始化执行完毕以后,通知主线程继续工作
final CountDownLatch countDownLatch = new CountDownLatch(2);
Callable 和Future使用
Futrue模式费用适合在处理耗时很长的业务逻辑进行使用,可以有效的减小系统的影响,
提高系统的吞吐量
public class UseFuture implements Callable<String>{
private String para;
public UseFuture(String para){
this.para = para;
}
/**
* 这里是真实的业务逻辑,其执行可能很慢
*/
@Override
public String call() throws Exception {
//模拟执行耗时
Thread.sleep(5000);
String result = this.para + "处理完成";
return result;
}
//主控制函数
public static void main(String[] args) throws Exception {
String queryStr = "query";
//构造FutureTask,并且传入需要真正进行业务逻辑处理的类,
//该类一定是实现了Callable接口的类
FutureTask<String> future =
new FutureTask<String>(new UseFuture(queryStr));
FutureTask<String> future2 =
new FutureTask<String>(new UseFuture(queryStr));
//创建一个固定线程的线程池且线程数为1,
ExecutorService executor = Executors.newFixedThreadPool(2);
//这里提交任务future,则开启线程执行RealData的call()方法执行
//submit和execute的区别:
//第一点是submit可以传入实现Callable接口的实例对象,
// 第二点是submit方法有返回值
Future f1 = executor.submit(future);
//单独启动一个线程去执行的
Future f2 = executor.submit(future2);
System.out.println("请求完毕");
try {
//这里可以做额外的数据操作,也就是主程序执行其他业务逻辑
System.out.println("处理实际的业务逻辑...");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
//调用获取数据方法,如果call()方法没有执行完成,则依然会进行等待
System.out.println("数据:" + future.get());
System.out.println("数据:" + future2.get());
executor.shutdown();
}
}
Semaphore信号量
可以控制系统的流量,拿到线程的信号量则访问否则等待
通过acquire和release来获取和释放线程
final Semaphore semp = new Semaphore(5);
3.锁的高级深化
Lock and Condition
使用synchronized关键字可以实现线程间的同步互斥工作
使用Lock对象也可以实现同步互斥
如果多个线程之间需要实现协作 使用Object的wait和nofity,notifyAll
在使用Lock的时候可以使用一个新的等待/通知的类Condition 只针对一个具体的锁
public class UseCondition {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void method1(){
try {
lock.lock();
System.out.println("当前线程:" +
Thread.currentThread().getName() + "进入等待状态..");
Thread.sleep(3000);
System.out.println("当前线程:" +
Thread.currentThread().getName() + "释放锁..");
condition.await(); // Object wait
System.out.println("当前线程:" +
Thread.currentThread().getName() +"继续执行...");
condition.signal(); //Object notify
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
ReentrantLock重入锁
private Lock lock = new ReentrantLock(boolean isFair);//是否为公平锁
ReentrantReadWriteLock读写锁
核心是实现读写分离 在都多写少的情况下 性能高于重入锁
public class UseReentrantReadWriteLock {
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private ReadLock readLock = rwLock.readLock();
private WriteLock writeLock = rwLock.writeLock();
public void read(){
try {
readLock.lock();
System.out.println("当前线程:" +
Thread.currentThread().getName() + "进入...");
Thread.sleep(3000);
System.out.println("当前线程:" +
Thread.currentThread().getName() + "退出...");
} catch (Exception 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 (Exception e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
}
锁的优化
- 避免死锁
- 减少锁的持有时间
- 减少锁的粒度
- 锁的分离
- 尽量使用无锁的操作 比如原子类操作