1.Volatile
volatile是一个关键字,用于在并发编程中修饰变量
volatile:java提供的一种弱同步机制
- 轻量的同步机制,用来确保将变量的更新通知到其他线程
- 保证可见性(禁止指令重排)、不保证原子性
如何保证可见性
- 变量声明为volatile类型后,编译器与运行时都会注意到这个变量时共享的,不会将该变量
上的操作和其他内存操作仪器重排序 - volatile变量不会被缓存在寄存器
指令重排
多线程环境中,线程交替运行,编译器优化重排的存在,两个线程中使用的变量无法保持一致性
JMM(Java内存模型)
JMM描述的是一组规范,通过规范来定义程序中各个变量的访问方式
jmm同步规定:
- 1.线程解锁前,必须把共享变量的值刷新回主内存
- 2.线程加锁前,必须读取主内存的最新值到自己的工作内存
- 3.加锁解锁是同一把锁
不保证原子性
原子性:不可分割、完整性,即某个线程正在做某个具体业务,中间不可以被加塞或者被分割,需要整体完整,
要么同时成功,要么同时失败
使用AtomicInteger,synchronized 可以保证原子性
2.CAS
CompareAndSet-- 比较并设置(交换)
CAS是一条CPU并发原语,体现在sun.misc.Unsafe类中各个方法
public final boolean CompareAndSet(int expectedValue,int newValue){
return U.CompareAndSetInt(this,VALUE,expectedValue,newValue);
}
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
原子变量是一种更好的“volatile”
原子变量比锁的力度更细,量级更轻
AtomicInteger,AtomicLong,AtomicBoolean,AtomicReference
CAS的缺点
- 1.循环时间长,开销大(CAS长时间不成功)
- 2.只能保证一个共享变量的原子操作
- 3.ABA问题
解决ABA问题
AtomicStampedReference
并发容器类(ConcurrentHashMap,CopyOnWriteArrayList)代替同步容器类(Hashtable,Vector)
CopyOnWriteArrayList的操作,读写分离
public boolean add(E e){
final ReentrantLock lock=this.lock();
lokc.lock();
try{
Object[] elements=getArray();
int len=elements.length;
Object[] newElements=Arrays.copyof(elements,len+1);
newElements[len]=e;
setArray(newElements);
return true;
}finally{
lock.unlock();
}
}
3.锁
公平锁,非公平锁
ReentrantLock 默认是非公平锁,但是可以设置称为公平锁
synchronized 是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 通过两个静态内部类来实现
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁(递归锁)
递归锁是指同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码
线程可以进入任何一个它已经拥有的锁所同步着的代码块
ReentrantLock/Synchronized 就是典型的可重入锁
重入锁进一步提升了加锁行为的封装性
独占锁(写锁)/共享锁(读锁)/互斥锁
独占锁:该锁一次只能被一个线程持有(ReentrantLock,Synchronized)
共享锁:该锁可以被多个线程所持有(ReentrantReadWriteLock的读锁)
自旋锁(spinlock)
尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁
锁 | synchronized | lock |
---|---|---|
原始构成 | JVM,底层使用monitor对象完成 | 具体类(java.util.concurrent.locks) |
中断 | 不可中断 | 可以中断 |
加锁公平性 | 非公平锁 | 默认是非公平,构造传入true可以实现公平 |
条件 | 无条件 | Condition |
4.AQS
AbstractQueuedSynchronizer
在java.util.concurrent
-
ReentrantLock
-
ReentrantReadWriteLock
-
SynchronousQueue
-
FutureTask
-
CountDownLatch
-
Semaphore
-
CyclicBarrier
CountDownLatch
- 1.它允许一个或多个线程一直等待,知道其他线程的操作执行完后再执行。例如,应用程序的主线程希望在负责 启动框架服务的线程已经启动所有的框架服务之后再执行
-
- CountDownLatch主要有两个方法,当一个或多个线程调用await()方法时,调用线程会被阻塞。其他线程调用 countDown()方法会将计数器减1,当计数器的值变为0时,因调用await()方法被阻塞的线程才会被唤醒,继续执行
Semaphore
可以代替Synchronize 和Lock
用于多个共享资源的互斥作用,另一个用于并发线程数的控制
CyclicBarrier
可循环(Cyclic)使用的屏障。让一组线程到达一个屏障(也可叫同步点)时被阻塞,知道最后一个线程到达屏 障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CycliBarrier的await()方法
可以设置循环使用
5.ThreadPool
阻塞队列
- ArrayBlockingQueue,一个基于数组结构的有界阻塞队列,FIFO
- LinkedBlockingQueue,个基于链表结构的阻塞队列,FIFO,吞吐量高于ArrayBlockingQueue
- SynchronousQueue,一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量较高
在多线程领域:所谓阻塞,在某些情况下会挂起线程,一旦满足条件,被挂起的线程又会自动被唤醒
使用阻塞队列BlockingQueue时我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都可自行处理
阻塞队列的各种方法
方法类型 | 阻塞 | 抛出异常 | 特殊值 | 超时 |
---|---|---|---|---|
插入 | put(e) | add(e) | offer(e) | offer(e,time,unit) |
移除 | take() | remove() | poll() | poll(time,unit) |
检查 | ~ | element() | peek() | ~ |
线程池
线程池主要是用来控制线程的数量,处理过程中将任务放进队列,然后在线程创建后启动给这些任 务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来 执行
- 线程复用,降低线程创建和销毁造成的的资源消耗
- 控制最大并发数
- 管理线程,提升响应速度,任务到达不需要等待线程创建的过程
几个线程池的实现
- Executors.newFixedThreadPool(int)
- Executors.newSingleThreadExecutor()
- Executors.newCachedThreadPool()
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
-
corePoolSize,线程池中常驻核心线程数
在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务
当线程池的线程数达到corePoolSize后,就会把到达的任务放到缓存队列当中 -
maximumPoolSize,线程池能够容纳同时执行的最大线程数,必须大于等于1
-
keepAliveTime,多余空闲线程的存活时间
-
unit, 存活的时间单位 (TimeUnit.)
-
workQueue,任务队列,被提交但是尚未被执行的任务
-
threadFactory,表示生成线程池中工作线程的线程工厂,默认即可
-
handler,拒绝策略
RejectedExecutionHandler 拒绝策略
当线程全部占用,等待队列已经全部排满,无法接纳新的任务,需要拒绝策略机制来合理的处理这个问题
-
AbortPolicy 默认,直接抛出RejectedExecutionException异常阻止系统正常运行
-
CallerRunsPolicy,将任务回退到调用者
-
DiscardOldestPolicy,抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
-
DiscardPolicy,直接丢弃任务
线程池创建
- 线程池不允许>使用Executors创建
- 通过ThreadPoolExecutor的方式,规避资源耗尽风险
- FixedThreadPool和SingleThreadPool允许请求队列长度为Integer.MAX_VALUE可能会堆积大量请求
- CachedThreadPool和ScheduledThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量线程,导致OOM
线程池的工作流程
配置线程池
- CPU密集型,大量的运算,基本不会出现阻塞,线程池=CPU核心数+1
int num_cpu=Runtime.getRuntime().availableProcessors(); // cpu的核心数
- IO密集型,会出现阻塞,尽可能多的配置多的线程
估算出任务的等待时间与计算时间的比值
CPU核心数/(1-阻塞系数) ,阻塞系数0.8~0.9之间