1 导读
基础知识
1 并发线程安全
1.1 JUC( java.util.concurrent ,java并发包)
1.2 JUC组件
juc 并发的原子类、集合、锁、tool(countdownlunch)、线程池相关(Future,Executor)
1 原子类
AtomicInteger:原子性的修改,基于(cas)
2 集合
1. CopyOnWriteArrayList(读多写少,不加锁,写不阻塞读,但是写会阻塞写,修改时复制一份,修改复制的新的,修改引用)
2. ConcurrentHashMap (hash,减少锁的范围,数组+链表+红黑树)
3. ConcurrentSkipListMap(跳表)
3 tool
- CountDownLatch:使当前线程在锁存器倒计数至零之前一直等待,countDown()方法计数减一,调用await()方法进行阻塞,等待在调用await的位置处(可以设置超时时间)
- CyclicBarrier:调用await()方法计数加1,未达到时阻塞,计数达到指定值时释放所有等待线程,可重复性
CountDownLatch和CyclicBarrier都有让多个线程等待同步然后再开始下一步动作的意思,但是CountDownLatch的下一步的动作实施者是主线程,具有不可重复性;而CyclicBarrier的下一步动作实施者还是“其他线程”本身,具有往复多次实施动作的特点。 - Semaphore:信号量,acquire()获取一个许可,release()释放一个许可,当达到指定数量时会阻塞
new Semaphore(3); - Exchanger:两个线程之间交换信息,exchanger.exchange(),主要用于管道通讯等两两交换比对的场景。
4 线程池相关:Executors.newFixedThreadPool(2),线程池工厂类
1. Future,FutureTask(异步)
2. Executor(线程池),Executors是其工厂类
3. TimeUnit
ExecutorService executorService = new ThreadPoolExecutor(1,1,10,TimeUnit.MINUTES,new LinkedBlockingQueue<>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
5 锁
1.3 高并发处理思路及手段
1.4 JMM
2 并发线程安全
2.1 线程安全性
原子性
AtomicBoolean 可以 让代码只执行一次
可见性
volatile不具有原子性,适合做状态标记量和双重检测标记
有序性
happens-before的八个原则
2.2 安全发布对象
双重检测机制不一定线程安全(指令重排序)
2.3 线程安全策略
- 不可变对象
- 线程封闭(只有一个线程能看到)
- 线程安全的类(StringBuffer,Vector,Stack,Hashtable,ConcurrentHashMap,Collections.synchronizedList等)同步容器每个方法是线程安全的,但是方法混合调用可能出现线程不安全的问题,同步容器性能差,而且不能完全做到线程安全,一般使用并发容器
- 线程不安全的类(simpleDateFormat.parse)
- 并发容器(JUC,java.util.concurrent)
修饰引用类型变量时,不允许指向其他对象,但是可以改变对象的值
并发容器
Skiplist
COW(读多写少,写的时候写到新的数据结构,牺牲数据实时性满足数据的最终一致性即可)
2.4 线程安全对象策略
2.5 JUC的AQS(AbstractQueuedSynchronizer抽象队列同步器)
AQS
aqs的等待队列(上)
condition的等待队列(下)
condition.await()从上面的队列加入下面
condition.signalAll();从下面的加入上面
AQS同步组建
-
CountDownLatch
-
Semaphore
-
CyclicBarrier
-
ReentramtLock
-
Condition
-
FutureTask
1. countDownLatch(等待所有线程执行完之后再执行之后的操作) countDownLatch.await(); countDownLatch.await(10, TimeUnit.MILLISECONDS); 2. Semaphore(控制并发访问的个数,仅能提供有限访问的资源) semaphore.acquire(); // 获取一个许可 semaphore.release(); // 释放一个许可 semaphore.acquire(3); // 获取多个许可 semaphore.tryAcquire()) // 尝试获取一个许可(拿不到许可直接丢弃) semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)) // 尝试获取一个许可,等待一秒 3. CyclicBarrier(多个线程相互等待,每个线程都准备就绪后才往后执行) 与countDownLatch区别 计数器可以重用 多个线程相互等待 barrier.await(); 4. ReentrantReadWriteLock(读多写少的时候,容易饥饿)(学) StampedLock(学) 选择: 少量竞争者的时候synchronized(不会死锁) 有其他ReentrantLock特有功能时使用ReentrantLock
2.6 JUC的组件拓展
FutureTask
Future获取线程返回值
FutureTask实现RunnableFuture,RunnableFuture继承了Runnable和Future
Fork / Join
工作窃取算法
// 执行子任务
leftTask.fork();
rightTask.fork();
// 等待任务执行结束合并其结果
int leftResult = leftTask.join();
int rightResult = rightTask.join();
BlockingQueue
适用场景:生产者消费者
线程池
Executors.newCachedThreadPool();
Executors.newScheduledThreadPool();调度,可以设置间隔时间执行
当线程池中的任务很小,小到任务执行时间和调度任务的时间都接近时,使用线程池速度反而会降低
多线程并发
解决方案
顺序加锁
锁加上时间
3.7 最佳实践
spring中controller,service等线程安全主要是无状态对象,
java7的Segment
java8引入红黑树,去掉Segment
3 高并发处理思路及手段
扩容
多个机房
异地多机房
缓存
1到4都可以加缓存
缓存读多写少,实时性低
缓存技术选型:本地缓存容易出现单机瓶颈,分布式缓存易于扩展
memcache服务端不支持分布式,依赖客户端的一致性hash算法做分布式
单个item最大的数据时1Mb
redis数据支持持久化,数据结构类型丰富,数据备份,高性能,原子性
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.maximumSize(10) // 最多存放10个数据
.expireAfterWrite(10, TimeUnit.SECONDS) // 缓存10秒
.recordStats() // 开启记录状态数据功能
.build(new CacheLoader<String, Integer>() {
@Override
// 默认的返回值
public Integer load(String key) throws Exception {
return -66;
}
});
- 缓存穿透
正常情况下,查询的数据都存在,如果请求一个不存在的数据,也就是缓存和数据库都查不到这个数据,每次都会去数据库查询,这种查询不存在数据的现象我们称为缓存穿透
解决方案缓存空对象 - 缓存击穿
在高并发的情况下,大量的请求同时查询同一个key时,此时这个key正好失效了,就会导致同一时间,这些请求都会去查询数据库,这样的现象我们称为缓存击穿
解决方案
采用分布式锁,只有拿到锁的第一个线程去请求数据库,然后插入缓存,当然每次拿到锁的时候都要去查询一下缓存有没有 - 缓存雪崩
当某一时刻发生大规模的缓存失效的情况
解决方案
设置不同的失效时间
采用缓存击穿的解决办法,加锁
永不失效,就是采用定时任务对快要失效的缓存进行更新缓存和失效时间
消息队列
不适用延迟很敏感以及强事务保证的场景
应用拆分
按功能模块
缺点
管理麻烦
网络开销
同步
rest(HTTP)
rpc(dubbo)
异步
消息队列
服务发现
zookeeper
服务挂了
熔断,限流,降级
限流
漏桶 VS 令牌桶
令牌桶可以应对突发请求,漏桶只能以恒定速率
- 单机版限流(Guava RateLimiter)
//每秒允许5个请求
private static RateLimiter rateLimiter = RateLimiter.create(5);
//间隔201毫秒获取令牌。获取不到就不获去了
rateLimiter.tryAcquire(201, TimeUnit.MILLISECONDS)
//能保证所有请求被处理
rateLimiter.acquire();
- 分布式限流
redis:incrby key value
降级熔断
通过配置系统(类似配置中心)的配置,确定是否降级,下图的db相当于数据字典