同步屏障,只有到达这个屏障的线程到达指定数目时,所有线程才能继续运行下去
在屏障前阻塞后,只有符合以下情况才能结束等待:
构造方法
CyclicBarrier(Integer parties);
CyclicBarrier(Integer parties, Runnable barrierAction);
第一个构造方法是调用第二个构造方法的
当到达屏障的线程数达到要求时,最后一个到达的线程会运行barrierAction
调用的方法是:
CyclicBarrier barrier = new CyclicBarrier(5);
barrier.await();
一个计数的闭锁,作用类似于CyclicBarrier。
用给定的计数初始化CountDownLatch,由于调用了await()方法,所以在当前计数通过countDown()方法到达零之前,await方法会一直受到阻塞。之后,会释放所有等待的线程。
这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用CyclicBarrier
· CountDownLatch:一个或多个线程,等待其他多个线程完成某件事情之后才能执行;
· CyclicBarrier: 多个线程互相等待,直到到达同一个同步点(屏障),再继续一起执行
两者的区别还在于,CountDownLatch 线程等待的是其他线程调用countDown方法;而CyclicBarrier 线程等待的是其他和自己一样等待的线程
控制访问多个共享资源的计数器。
Semaphore维护了一个信号量许可集。线程可以获取信号量的许可。如果信号量中有可用的许可,则可以继续运行,否则必须等待,直到有许可为止。
类似停车场停车位。有如果没有空位了,其他车就只能等到其他车离开,才能停车。
Semaphore常用语约束一些(物理或逻辑)资源的线程数量
当信号量初始化为1时,就相当于是互斥锁。
HashMap线程不安全,而HashTable效率又太低
JDK7时,ConcurrentHashMap采用了分段所的概念。
JDK8时,改成了CAS+Synchronized来保证并发更新的安全。
当然底层还是数组+链表+红黑树的结构
JDK7版本的ConcurrentHashMap
由Segment数组组成,初始化时可以设置Setment数,默认为16,初始化后不可扩容。相当于每个Segment都是一个HashTable。每次锁住的都是一个Segment。所以叫分段锁。
JDK7时,HashMap由数组+链表组成;而JDK8时,HashMap由数组+链表+红黑树组成
区别在于,JDK8时,在链表长度超过8时,会将链表转换为红黑树,小于6时,又会将红黑树转回链表。
是一个基于链接节点的无边界的线程安全队列,遵循队列的FIFO先进先出原则,采用CAS算法来实现的
注意:
有两种情况会产生阻塞:
BlockingQueue对插入操作、一处操作、获取元素操作提供了四种不同的方法用于不同场景的使用
操作 抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用
BlockingQueue是接口,接下来介绍它的几个实现类
一个由数组实现的有界阻塞队列。有界且固定,其大小在构造时由构造函数决定
支持对等待的生产者线程和使用者线程进行排序。可以选择公平或者不公平策略
和ArrayBlockingQueue使用方式基本一致
区别:
ArrayBlockingQueue是一个由数组支持的有界阻塞队列
队列中的锁是没有分离的,即生产和消费用的是同一个锁
生产或消费时,直接将对象插入或移除
必须指定大小
LinkedBlockQueue是一个由链表支持的队列(可设置)阻塞队列
锁是分离的,生产用的是putLock,消费用的是takeLock
需要将对象转换成Node,再插入或移除
不需要指定大小
优先级队列。在java.util.PriorityQueue基础上提供了可阻塞的读取操作
特点:
实际上不是一个真正的队列,因为它不存储元素。它维护一组线程,这些线程在等待着添加或移除元素。它永远阻塞着,直到有线程要put并且有线程要take时,才会将元素转交一下。
线程是一个程序员一定会涉及到的概念,但是线程的创建和切换的代价都是比较大的所以我们需要一个比较好的方案能够做到线程的复用。这里就涉及到一个概念:线程池。河里使用线程池有三个明显的好处:
主要通过ThreadPoolExecutor来完成
变量ctl定义为AtomicInteger, 记录了“线程池中的任务数量”和“线程池的状态”两个信息。工32位。其中高3位表示线程池状态,低29位表示线程池中的任务数量
线程池状态:
构造方法参数:
corePoolSize:核心线程数量
maximumPoolSize:允许的最大线程数量。只有选择的阻塞队列有界才有效
keepAliveTime:线程空闲时间。只有线程数大于corePoolSize才生效
unit:时间的单位
workQueue:阻塞队列
threadFactory:创建线程的工厂。可以设置线程的名字。默认都是非守护线程,线程优先级也是默认的。
handler:线程的拒绝策略:就是线程满了,而且队列也满了,线程池会选择一种拒绝策略来处理
AbortPolicy:直接抛出异常,默认策略
CallerRunsPolicy:用调用者所在的线程来执行任务
DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务
DiscardPolicy:直接丢弃任务
当然也可以实现自己的拒绝策略,例如记录日志等。实现RejectedExecutionHandler接口即可
复用固定数量的线程处理一个共享的“无边界队列”
其定义如下:
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
最大线程数和核心线程数都设置成nThreads了。不会创建新的线程,而是都放在阻塞队列中。
执行方式:
ExecutorService exec = Executors.newFixedThreadPool(5);
exec.execute(new Runnabe(){
public void run(){}
});
使用单个工作线程来执行一个无边界的队列
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>());
}
核心线程为0,最大线程为Integer.MAX_VALUE,意味着所有线程一来就会加入到阻塞队列中。因为线程池的基本大小为0,所以一般情况下线程池中没有空闲的线程。
阻塞队列采用的是SynchronousQueue,这是一个没有元素的阻塞队列
这种线程池,用于执行大量短生命周期的异步任务。在60s内,线程被销毁前,可以多次重用。当任务高峰期结束后,则会自动销毁。
要注意控制并发的任务数,避免创建过多的线程,耗尽系统内存
继承ThreadPoolExecutor,且实现了ScheduledExecutorService。相当于提供了“延迟”和“周期执行”功能的ThreadPoolExecutor。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);// 核心线程数
// 五秒后执行
scheduledThreadPool.schedule(new Runnable(){},5,TimeUnit.SECONDS);
// 关闭线程池
scheduledThreadPool.shutdown();