java 并发(并发工具包)
##13个原子操作类
####原子基本类型
- AtomicBoolean
- AtomicInteger
- AtomicLong
常用方法如下:
int addAndGet(int delta): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值
int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值
int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值
####原子数组
AtomicIntegerArray:原子更新整型数组里的元素
AtomicLongArray:原子更新长整型数组里的元素
AtomicReferenceArray:原子更新引用类型数组里的元素
常用方法如下:
int addAndGet(int i , int delta):以原子方式将输入值与数组中索引i的元素相加。
boolean compareAndSet(int i , int expect , int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。
####原子更新引用
AtomicReference 原子更新引用类型
AtomicReferenceFieldUpdater 原子更新引用类型里的字段
####原子更新属性
AtomicIntegerFieldUpdater:
AtomicLongFieldUpdater
AtomicStampedReference:
参考:https://www.cnblogs.com/boothsun/p/8010692.html
##并发工具类
- CountDownLatch
利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行
常用api
public void await() //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { }; //将count值减1
-
CyclicBarrie(同步屏障)
常用api
int await()
int await(long timeout, TimeUnit unit) -
CyclicBarrier和CountDownLatch的区别
- CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的
-
Semaphore
Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可
常用API
1. acquire()
2. release()
3. intavailablePermits():返回此信号量中当前可用的许可证数
4. intgetQueueLength():返回正在等待获取许可证的线程池
5. booleanhasQueuedThreads():是否有线程正在等待获取许可证
6. Collection getQueuedThreads():返回所有等待获取许可证的线程集合
##阻塞队列
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列
- DelayQueue:一个使用优先级队列实现的无界阻塞队列
- SynchronousQueue:一个不存储元素的阻塞队列
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
阻塞队列提供了四种处理方法:
- 抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue full”)异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。
- 返回特殊值:插入方法会返回是否成功,成功则返回true。移除方法,则是从队列里拿出一个元素,如果没有则返回null
- 一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用
- 超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出
###阻塞队列的实现原理
public ArrayBlockingQueue(int capacity, boolean fair) {
//省略其他代码
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
insert(e);
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return extract();
} finally {
lock.unlock();
}
}
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
##线程池
###线程池的处理流程
- 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程
- 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程
- 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务
###基础参数
- corePoolSize(线程池的基本大小)
- maximumPoolSize(线程池最大数量)
- runnableTaskQueue(任务队列)
- new ThreadFactoryBuilder().setNameFormat(“XX-task-%d”).build(); 设置线程池名称前缀
- keepAliveTime(线程活动保持时间)
- RejectedExecutionHandler(拒绝策略)
###关闭线程池
- shutdown 函数 (只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
- shutdownNow 函数 首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
- isTeminated 函数 返回为true 表示线程池关闭成功
##CopyOnWrite组件
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
##Executor框架
###ThreadPoolExecutor
- FixedThreadPool 推荐使用
- SingleThreadExecutor 单个线程
- CachedThreadPool. 注意线程不够使用的话,会不断创建需要线程,导致资源消耗完
###ScheduledThreadPoolExecutor
class Temp extends Thread {
public void run() {
System.out.println("run");
}
}
public class ScheduledJob {
public static void main(String args[]) throws Exception {
Temp command = new Temp();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> scheduleTask = scheduler.scheduleWithFixedDelay(command, 5, 1, TimeUnit.SECONDS);
}
}
###Runnable接口和Callable接口
Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。它们之间的区别是Runnable不会返回结果,而Callable可以返回结果
##高并发限流策略
###Guava-RateLimiter(令牌捅算法)
令牌桶算法(Token Bucket)和 Leaky Bucket(漏捅) 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务
令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量.
缺点:单机限流,在分布式限流中需要结合redis限流重写代码
###源码分析:
####SmoothBursty
RateLimiter
**RateLimiter 中的变量**
/**
* The currently stored permits.
*/
现存储的令牌数量
double storedPermits;
/**
* The maximum number of stored permits.
*/
最大线程数量
double maxPermits;
/**
* The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits
* per second has a stable interval of 200ms.
*/
发送一个令牌的间隔
double stableIntervalMicros;
/**
* The time when the next request (no matter its size) will be granted. After granting a
* request, this is pushed further in the future. Large requests push this further than small
* requests.
*/
下次可以获取令牌的时间 毫秒
private long nextFreeTicketMicros = 0L; // could be either in the past or future
SmoothBursty
static final class SmoothBursty extends SmoothRateLimiter {
/** The work (permits) of how many seconds can be saved up if this RateLimiter is unused? */
//最多存储多少秒的令牌
final double maxBurstSeconds;
SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
super(stopwatch);
this.maxBurstSeconds = maxBurstSeconds;
}
@Override
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
double oldMaxPermits = this.maxPermits;
//设置最大令牌数
maxPermits = maxBurstSeconds * permitsPerSecond;
//设置现在令牌数量
if (oldMaxPermits == Double.POSITIVE_INFINITY) {
// if we don't special-case this, we would get storedPermits == NaN, below
storedPermits = maxPermits;
} else {
storedPermits = (oldMaxPermits == 0.0)
? 0.0 // initial state
: storedPermits * maxPermits / oldMaxPermits;
}
}
@Override
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
return 0L;
}
}
获取令牌逻辑
设置睡眠多久获取到所要请求的令牌
public double acquire(int permits) {
long microsToWait = reserve(permits);
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
final long reserve(int permits) {
checkPermits(permits);
synchronized (mutex()) {
//返回获取到足够令牌所需要的等待时间
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
private void resync(long nowMicros) {
// if nextFreeTicket is in the past, resync to now
if (nowMicros > nextFreeTicketMicros) {
//设置到现在可以获取到的令牌数
storedPermits = min(maxPermits,
storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros);
nextFreeTicketMicros = nowMicros;
}
}
@Override
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
//计算设置到当前还可以有多少个令牌可以获取
resync(nowMicros);
//记录上一次可获取令牌时间
long returnValue = nextFreeTicketMicros;
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
//计算得到可能不够的令牌数量
double freshPermits = requiredPermits - storedPermitsToSpend;
//storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) 返回固定是0 ,计算得到需要等待的时间
long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
this.nextFreeTicketMicros = nextFreeTicketMicros + waitMicros;
this.storedPermits -= storedPermitsToSpend;
//返回的是之前的可获取令牌时间,如果不想预拿令牌,需要将nextFreeTicketMicros返回即可
return returnValue;
}
参考:
https://segmentfault.com/a/1190000012947169 基于redis的RateLimiter
http://ifeve.com/guava-ratelimiter/
https://www.cnblogs.com/softidea/p/6229543.html
https://segmentfault.com/a/1190000012875897