J.U.C 概览
J.U.C 概览
JSR,全称
Executor 框架(线程池、 Callable 、Future)
简单的说,就是一个任务的执行和调度框架,涉及的类如下图所示:
其中,最顶层是
public interface Executor {
void execute(Runnable command);
}
另外,我们还可以看到一个
任务就是实现
- newCachedThreadPool:大小不受限,当线程释放时,可重用该线程;
- newFixedThreadPool:大小固定,无可用线程时,任务需等待,直到有可用线程;
- newSingleThreadExecutor:创建一个单线程,任务会按顺序依次执行;
- newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行
简单示例如下:
ExecutorService executor = Executors.newCachedThreadPool();//创建线程池
Task task = new Task(); //创建Callable任务
Future<Integer> result = executor.submit(task);//提交任务给线程池执行
result.get();//等待执行结果; 可以传入等待时间参数,指定时间内没返回的话,直接结束
最后我们讨论下批量任务的执行方式:
- 首先定义任务集合,然后定义
Future 集合用于存放执行结果,执行任务,最后遍历Future 集合获取结果。优点:可以依次得到有序的结果;缺点:不能及时获取已完成任务的执行结果; - 首先定义任务集合,通过
CompletionService 包装ExecutorService ,执行任务,然后调用其take() 方法去取Future 对象。优点:及时得到已完成任务的执行结果,缺点:不能依次得到结果。
在方式一中,从集合中遍历的每个
AbstractQueuedSynchronizer(AQS 框架)
非公平锁的
final void lock() {
// CAS 操作,如果 State 为 0(表示当前没有其它线程占有该锁),则将它设置为1
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
首先是不管先后顺序,直接尝试获取锁(非公平的体现
可重入
什么是可重入锁,不可重入锁呢
一个线程获取多少次锁,就必须释放多少次锁。这对于内置锁也是适用的,每一次进入和离开
Locks & Condition(锁和条件变量)
先看一下
lock() 等待获取锁lockInterruptibly() 可中断等待获取锁,synchronized 无法实现可中断等待tryLock() 尝试获取锁,立即返回true 或false tryLock(long time, TimeUnit unit) 指定时间内等待获取锁unlock() 释放锁newCondition() 返回一个绑定到此Lock 实例上的Condition 实例
关于
-
ReentrantLock:可重入锁,所谓的可重入锁,也叫递归锁,是指一个线程获取锁后,再次获取该锁时,不需要重新等待获取。
ReentrantLock 分为公平锁和非公平锁,公平锁指的是严格按照先来先得的顺序排队等待去获取锁,而非公平锁每次获取锁时,是先直接尝试获取锁,获取不到,再按照先来先得的顺序排队等待。 -
ReentrantReadWriteLock:可重入读写锁,指的是没有线程进行写操作时,多个线程可同时进行读操作,当有线程进行写操作时,其它读写操作只能等待。即“读
- 读能共存,读- 写不能共存,写- 写不能共存”。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。
Condition.await 对应于Object.wait ;Condition.signal 对应于Object.notify ;Condition.signalAll 对应于Object.notifyAll ;
使用
Synchronizers(同步器)
闭锁CountDownLatch
闭锁主要用于让一个主线程等待一组事件发生后继续执行,这里的事件其实就是指
在
栅栏CyclicBarrier
栅栏主要用于等待其它线程,且会阻塞自己当前线程,所有线程必须同时到达栅栏位置后,才能继续执行;且在所有线程到达栅栏处,可以触发执行另外一个预先设置的线程,具体如下图所示:
在上图中,T1、T2、
信号量Semaphore
信号量主要用于控制访问资源的线程个数,常常用于实现资源池,如数据库连接池,线程池。在
交换器Exchanger
交换器主要用于线程之间进行数据交换;当两个线程都到达共同的同步点(都执行到
Atomic Variables(原子变量)
原子变量主要是方便程序员在多线程环境下,无锁的进行原子操作;原子类是基于
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
在
- 原子方式更新基本类型:AtomicInteger、
AtomicLong 等 - 原子方式更新数组:AtomicIntegerArray、
AtomicLongArray 等 - 原子方式更新引用:AtomicReference、 AtomicReferenceFieldUpdater…
- 原子方式更新字段:AtomicIntegerFieldUpdater、
AtomicStampedReference( 解决CAS 的ABA 问题)
BlockingQueue(阻塞队列)
阻塞队列提供了可阻塞的入队和出对操作,如果队列满了,入队操作将阻塞直到有空间可用,如果队列空了,出队操作将阻塞直到有元素可用;
在
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个支持延时获取元素的无界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
Concurrent Collections(并发容器)
说到并发容器,不得不提同步容器,在
- 同步容器对所有容器状态的访问都串行化,严重降低了并发性;
- 某些复合操作,仍然需要加锁来保护
- 迭代期间,若其它线程并发修改该容器,会抛出
ConcurrentModificationException 异常,即快速失败机制
对于复合操作,我们可以举个例子
public static Integer getLast(Vector<Integer> list){
int lastIndex = list.size() - 1;
if(lastIndex < 0) return null;
return list.get(lastIndex);
}
在以上代码中,虽然
ConcurrentHashMap
另外,性能是我们比较关心的,我们可以与同步容器做个对比,如下图所示:
CopyOnWriteArrayList/Set
也叫拷贝容器,指的是写数据的时候,重新拷贝一份进行写操作,完成后,再将原容器的引用指向新的拷贝容器。适用情况:当读操作远远大于写操作的时候,考虑用这个并发集合。
Fork/Join 并行计算框架
其实对于使用
if (任务足够小){
直接执行该任务;
}else{
将任务拆分成多个子任务;
执行这些子任务并等待结果;
}
TimeUnit 枚举
Thread.sleep(2400000)// 可读性差
在
TimeUnit.SECONDS.sleep(4);
TimeUnit.MINUTES.sleep(4);
TimeUnit.HOURS.sleep(1);
TimeUnit.DAYS.sleep(1);
另外,
TimeUnit.SECONDS.toMillis(44);// 44,000