JUC
提供并发编程的解决方案
CAS是java.util.concurrent.atomic包的基础
AQS是java.util.concurrent.locks包以及一些常用类比如Semophore,ReentrantLock等类的基础
使用node实现FIFO队列,可以用于构建锁或者其他的同步装置的基础框架
利用了一个类型表示状态
使用方法为继承
子类通过继承并通过实现他的方法管理其状态(acquire和release)的方法操纵状态
可以同时实现排他锁和共享锁模式(独占、共享)
aqs维护一个CLH队列来管理锁,线程会首先尝试获取锁,如果失败,则将当前线程与等待状态等信息变为一个node节点加入到sync queue,接着不断循环尝试获取锁,当前结点为head的直接后继才会尝试,如果失败,则会阻塞自己直到被唤醒,当持有锁的线程释放锁的时会唤醒队列中的后继线程
分类
线程执行器executor
锁locks
原子变量类atomiic
并发工具类tools
并发集合collections
线程池(Executors)
种类 | 说明 |
newFixThreadPool(int nThreads) | 指定工作线程数量的线程池 |
newCachedThreadPool() | 处理大量短时间工作任务的线程池 (1)试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程 (2)如果线程闲置的时间超过阈值,则会被终止并移出线程池 (3)系统长时间闲置时候,不会消耗什么资源 |
newSingleThreadExecutor() | 创建唯一的工作线程来执行任务,如果异常结束,就用另一个线程替换 |
newSingleThreadScheduledExecutor() newSchduledExecutor(int n) | 完成定时或周期性的工作调度 |
newWorkStrealingPool() | 内部构建ForkJoinPool,work-strealing算法,并行处理任务,不保证顺序处理 |
fork/join框架1.7引入
把大任务分割成若干个小任务并行执行,最终汇总每个小任务结果得到大任务结果的框架
work-stealing:某个线程从其他队列里窃取任务来执行
降低资源消耗、提高线程的可管理性
简单例子:
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
public static final int threshold = 2;
private int start;
private int end;
public ForkJoinTaskExample(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
boolean canCompute = (end - start) <= threshold;
if (canCompute) {
for (int i = start; i < end; i++) {
sum += i;
}
} else {
//拆分为两个子任务计算
int middle = (start + end) / 2;
ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
//执行子任务
leftTask.fork();
rightTask.fork();
//等待任务执行结束并合并结果
int leftResult = leftTask.join();
int rightResult = rightTask.join();
//合并子任务
sum = leftResult + rightResult;
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
Future<Integer> result = forkJoinPool.submit(task);
System.out.println(result.get());
}
}
Executor框架示意图
构造函数如下,对应上图进行一个说明,比如newFixedThreaPool,他其实是一个ThreadPoolExecutor类,该类继承AbstractExecutorService类,该类实现了ExecutorService接口,而ExecutorService接口又继承Executor接口
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
JUC三个Executor接口
Executor:运行新任务的简单接口,将任务提交和任务细节解耦,只定义了void execute(Runnable command)这个方法
ExecutorService:具备管理执行器和任务生命周期的方法,任务机制更为完善
SchduledExecutorService:支持Future和定期执行任务
ThreadPoolExecutor结构
workQueue负责接收任务,workThread则是处理从workqueue里获取的实现runnable接口的任务,ThreadFactory则负责管理工作线程,并且也有相应的拒绝任务的机制
主要组成部分
corePoolSize:核心线程数
maximumPoolSize:线程不够用时能够创建的最大线程数
workQueue:任务等待队列
keepAliveTime:当线程池的线程数量大于corePoolsize时,超过keepAliveTime后,其他的线程会被销毁,仅保证核心线程数
threadFactory:创建新线程,Executors.defaultThreadFactory(),代码如下
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
handler:线程池饱和策略
AbortPolicy:直接抛出异常,默认策略
CallerRunsPolicy:用调用者所在线程来执行任务
DiscardOldestPolicy:丢弃队列中最靠前的任务,并执行当前任务
DiscardPolicy:直接丢弃任务
实现RejectedExecutionHandler接口的自定义handler
新任务提交execute执行后的判断
如果运行的线程少于corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;
如果线程池中的线程数量大于等于corePoolSize且小于maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务
如果设置的corePoolSize和maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取出任务并处理
如果运行线程数量大于等于maximumPoolSize,如果工作队列满了,则通过handler所指定的策略来处理任务
线程池状态
running:能接受新提交任务,并且也能处理阻塞队列中的任务
shutdown:不再接受新提交任务,但可以处理存量任务
stop:不再接受新提交的任务,也不处理存量任务
tidying:所有任务已经终止
terminted:terminated()方法执行后进入改状态
工作线程的生命周期
线程池的大小选定
cpu密集型:线程数=按照核数或核数+1设定
i/o密集型:线程数=cpu核数*(1+平均等待时间/平均工作时间)
new Thread的弊端:
新建对象,性能差
线程缺乏统一的管理,可能无限制新建线程,相互竞争,有可能占用过多的线程资源导致死机或OOM
缺乏更多的功能,如更多执行,顶期执行,线程中断
线程池好处:
重用存在的线程,减少对象创建、消亡的开销,性能佳
可有效控制最大并发线程数,提高系统资源利用率,同时避免过多资源竞争,避免阻塞
提供定时执行、定期执行、单线程、并发数控制等功能
locks
引入显式锁,方便对线程共享资源做更细粒度的锁控制
Condition对象由lock对象创建,并且可以创建多个,lock和condition都是通过aqs实现的,aqs通过lockSupport类的unpark,park来实现线程的唤醒、阻塞。condition可以理解为条件队列,即同一个锁可以根据不同条件来进行线程唤醒和阻塞,而不像synchorized那样,不同条件也会阻塞
可指定公平锁还是非公平锁
提供Condition,可分组唤醒需要唤醒的线程
提供可以中断等待锁的线程机制,lock.lockInterruptibly()
代码例子
package com.company.lock;
import java.util.ArrayDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author lulu
* @Date 2019/7/17 20:25
*/
public class TestLock {
//定义一个锁
final Lock lock = new ReentrantLock();
//代表需要生产的情况
final Condition gen = lock.newCondition();
//代表需要消费的情况
final Condition take = lock.newCondition();
private ArrayDeque arrayDeque = new ArrayDeque();
private volatile static Boolean stop = false;
public static void main(String args[]) throws InterruptedException {
TestLock lock = new TestLock();
TestLock.Producer p = lock.new Producer();
TestLock.Producer p1 = lock.new Producer();
TestLock.Customer c = lock.new Customer();
TestLock.Customer c1 = lock.new Customer();
p.start();
p1.start();
c.start();
c1.start();
TimeUnit.MILLISECONDS.sleep(10);
stop = true;
}
class Customer extends Thread {
@Override
public void run() {
while (!stop) {
lock.lock();//加入aqs的sync 队列
while (arrayDeque.isEmpty()) {
System.out.println("队列空,等待生产");
try {
take.await();//移出sync队列,此时加入take的condition等待队列,对应释放锁,需要信号才可以释放
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + "消费一个元素");
arrayDeque.poll();
gen.signalAll();//唤醒在等待的生产condition队列去获取锁
lock.unlock();
}
}
}
class Producer extends Thread {
@Override
public void run() {
while (!stop) {
lock.lock();//由于其他线程释放锁,当前线程加入aqs的sync队列
while (arrayDeque.size() == 5) {
System.out.println("队列满,等待消费");
try {
gen.await();//相当于Object.wait方法,直到gen signal选中才继续执行
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + "加入一个元素");
arrayDeque.offer("");
take.signalAll();//发送信号,如果condition里面有等待的线程节点,则加入sync对列,但没有被唤醒
lock.unlock();//释放锁,按从头到尾唤醒sync队列的线程
}
}
}
}
ReentranReadWriteLock(支持读读操作,适用于读多于写的并发场景)
atomic
各种操作的原子性,具有原子操作的类,使用CAS更新。详情点这里
tools(并发工具类)
闭锁CountDownLatch:
让一个主线程等待一组事件发生后继续执行
事件是指CountDownLatch的countDown()方法,举个不是很切合的例子,中转站快递投递通知,一个用户买了三个商品,需要都到达某个中转战时,相应的快递通知才会被发送,商品1到达后,发送给用户已经到达中转站,然后继续运输,当用户收到三个商品都到中转站通知时,通知就可以关闭了
package com.p_thread.collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
/**
* @author lulu
* @Date 2019/7/17 22:10
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
test();
}
private static void test() throws InterruptedException {
CountDownLatch c=new CountDownLatch(3);
IntStream.rangeClosed(1,3).mapToObj(i->new Task(c,i)).forEach(e->e.start());
c.await();
System.out.println("所有线程到达,开始执行主线程逻辑");
}
static class Task extends Thread{
private CountDownLatch c;
public Task(CountDownLatch c,int i){
super(i+"");
this.c=c;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep((long)Math.random()*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"到达该点");
c.countDown();
}
}
}
栅栏CyclicBarrier:
阻塞当前线程,等待其他线程
1.等待其他线程,且会阻塞自己当前线程,所有线程必须同时到达栅栏的位置后,才能继续执行
2.所有线程到达栅栏处,可以触发执行另一个预先设置的线程
以刚刚快递投递信息发送的通知为例子,所有商品到达中转站后,只能停留,等所有到达后,快递通知才会被发送,而后再进行运输
代码例子,该代码把单线程和多线程做一个比较,在数据量较少时,单线程执行效率高,当数据量大的时候,多线程优势才展现出来,这里要把jvm参数-Xmx -Xms调高一点
package com.p_thread.collections;
import java.util.Arrays;
import java.util.concurrent.*;
/**
* @author lulu
* @Date 2019/5/18 0:21
*/
public class CyclicBarrierDemo {
/* public static void test() throws ExecutionException, InterruptedException {
FutureTask t=new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
TimeUnit.SECONDS.sleep(5);
return "ok";
}
});
new Thread(t).start();
System.out.println("线程开启");
System.out.println(t.get());
ExecutorService s=Executors.newSingleThreadExecutor();
Future f= s.submit(new Callable() {
@Override
public Object call() throws Exception {
TimeUnit.SECONDS.sleep(5);
return "ok";
}
});
System.out.println(f.get());
s.shutdown();
}*/
public static void main(String args[]){
/* try {
test();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}*/
int size = 400000000;
//定义数组
int[] numbers = new int[size];
//随机初始化数组
for (int i = 0; i < size; i++) {
numbers[i] = (int)(Math.random()*10);
}
//单线程计算结果
System.out.println();
long start = System.currentTimeMillis();
Long sum = 0L;
for (int i = 0; i < size; i++) {
sum += numbers[i];
}
long end=System.currentTimeMillis();
System.out.println("单线程计算结果:" + sum+"耗时:"+(end-start)+"");
//多线程计算结果
//定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
//定义5个Future去保存子数组计算结果
final int[] results = new int[5];
//定义一个循环屏障,在屏障线程中进行计算结果合并
long start1 = System.currentTimeMillis();
CyclicBarrier barrier = new CyclicBarrier(5, () -> {
int sums = 0;
for (int i = 0; i < 5; i++) {
sums += results[i];
}
long end1=System.currentTimeMillis();
System.out.println("多线程计算结果:" + sums+"耗时:"+(end1-start1)+"");
});
//子数组长度
int length = 80000000;
//定义5个线程去计算
for (int i = 0; i < 5; i++) {
//定义子数组
int[] subNumbers = Arrays.copyOfRange(numbers, (i * length), ((i + 1) * length));
//计算结果
int finalI = i;
executorService.submit(() -> {
for (int j = 0; j < subNumbers.length; j++) {
results[finalI] += subNumbers[j];
}
//等待其他线程进行计算
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
}
//关闭线程池
executorService.shutdown();
}
}
信号量Semaphore:
控制某个资源可被同时访问的线程个数
例子:运行结果实先出现那五个线程,然后再出现后面2个线程的打印信息
package com.p_thread.collections;
import java.util.Random;
import java.util.concurrent.*;
/**
* @author lulu
* @Date 2019/7/17 22:28
*/
public class SemaphoreDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService e = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(7);
final Semaphore semp = new Semaphore(5);
long start = System.currentTimeMillis();
for (int i = 0; i < 7; i++) {
e.submit(new Task(semp, countDownLatch));
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println(end - start);
e.shutdown();
}
static class Task implements Runnable {
private Semaphore semaphore;
private CountDownLatch countDownLatch;
public Task(Semaphore semaphore, CountDownLatch countDownLatch) {
this.semaphore = semaphore;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
semaphore.acquire();
long s = (long) (new Random().nextInt(5) + 1);
System.out.println(Thread.currentThread().getName() + "执行" + s + "秒");
TimeUnit.SECONDS.sleep(s);
semaphore.release();
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
交换机Exchanger:
两个线程到达同步点后,相互交换数据,下图为Thread1和Thread2,并非都是Thread1
例子:
package com.p_thread.collections;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @author lulu
* @Date 2019/7/17 22:53
*/
public class ExchangerDemo {
static class Task {
public Task(int math,int english){
this.math=math;
this.english=english;
}
private int math;
private int english;
}
public static void main(String[] args) {
final Exchanger exchanger=new Exchanger();
ExecutorService service= Executors.newFixedThreadPool(2);
service.execute(()->{
Object a= null ;
try {
a = exchanger.exchange(null);
} catch (InterruptedException e) {
e.printStackTrace();
}
Task b=(Task)a;
System.out.println("线程1汇报:处理完成,总分"+(b.english+b.math));
Object c= null;
try {
c = exchanger.exchange("我完成了");
System.out.println("线程1收到线程2消息--"+c);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(()->{
try {
System.out.println("线程2:我准备阻塞了,帮我做一下");
Object b=exchanger.exchange(new Task(99,100));
TimeUnit.SECONDS.sleep(3);
Object x1=exchanger.exchange("线程2收到了");
System.out.println("线程2收到线程1消息--"+x1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.shutdown();
}
}
collections
不允许插入null键,利用CAS和synchronized实现线程安全,比起以前的分段锁锁粒度更新
插入逻辑:
1.判断Node[]数组(bucket)是否初始化,没有则进行初始化操作
2.通过hash定位数组的索引坐标,是否有node节点,没有则进行CAS添加(链表的头节点),添加失败则进入下次循环
3.检查到内部正在扩容则帮助扩容
4.如果头节点不为空,则使用synchronized锁住头节点
5.判断链表长度是否到达8,如果到达的话则把该链表树化
blockingQueue:阻塞队列,队列满入队、队列空出队均会阻塞
throws Exception如果不能马上执行,抛出一个异常
special value如果不能马上执行,返回一个特殊值(true或false)
blocks不能马上执行阻塞
timesout超时即返回特殊值(一般为true或false)
ArrayBlockQueue先进先出,有界
DelayQueue,阻塞内部元素,里元素要实现delayed接口,该接口继承comparable,需要进行排序,一般对元素的过期时间的优先级进行排序,定时关闭链接,缓存对象,超时处理等等,内部实现ReentranLock+PriorityQueue
LinkedBlockingQueue:链表实现,其他和ArrayBlockQueue一致
PriorityBlockingQueue:无边界,允许插入空对象,元素实现comparable接口,优先队列
SynchronousQueue:容量为一的队列
copyonwrite:读写分离,最终一致性,使用时开辟新空间操作解决冲突,写操作加锁。