一 并发编程挑战
1.上下文切换
含义:
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
如何减少上下文切换:
1)无锁并发
2)CAS
3)使用最少线程
4)协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
2.死锁
避免死锁方法:
1)避免一个线程同时获取多个锁。
2)避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
3)尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
4)对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
二 并发机制底层实现原理
1.volatile原理
作用:如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的
原理: 从汇编之后的代码可看出volatile修饰变量写操作会有一个Lock操作
对volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在的缓存行的数据写回至系统内存。这时,其他处理器缓存的值是旧的,因此,在多处理器下,为保证各个处理器的缓存一致,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里;
两个实现原则:
1)Lock前缀指令会引起处理器缓存回写到内存
2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效
2.synchronized原理
使用方式:
1)对于普通同步方法,锁是当前实例对象
2)对于静态同步方法,锁是当前类的Class对象
3)对于同步方法块,锁是Synchonized括号里配置的对象
原理:
①JVM基于进入和退出Monitor对象来实现方法同步和代码块同步;
②代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的;
③monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁;
锁升级与对比:
从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级
3.原子类实现原理
使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作,处理器会自动保证基本的内存操作的原子性;
CAS存在的三大问题
①ABA问题
②循环时间长开销大:自旋
③只能保证一个共享变量的原子操作
三 内存模型
并发编程两个关键问题:通信 + 同步
JVM抽象结构图:
JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证
1.指令重排

1:编译器重排序
2 3:处理器重排序
Happen-Before原则:
1)程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
2)监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
3)volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
4)传递性:如果A happens-before B,且B happens-before C,那么A happens-before C
5)start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作
6)join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回
ReentrantLock
类图:
公平锁的加锁过程:
1)ReentrantLock:lock()
2)FairSync:lock()
3)AbstractQueuedSynchronizer:acquire(int arg)
4)ReentrantLock:tryAcquire(int acquires)
解锁过程:
1)ReentrantLock:unlock()
2)AbstractQueuedSynchronizer:release(int arg)
3)Sync:tryRelease(int releases)
四 并发编程基础
1.概念
2.优先级
3.线程状态
4.Daemon线程
5.基本操作
启动终止
中断操作
安全终止线程方式:标记字段
6.线程通信
volatile
synchronized
wait+notify
管道输入/输出流
join
ThreadLocal
【案例】
1.简单数据库连接池实例
2.Web服务器Demo
五 锁
1.Lock锁
Lock lock = new ReentrantLock()
①对比Synchronized的特性:
②API:
2.队列同步器 AbstractQueuedSynchronizer
队列同步器AbstractQueuedSynchronizer(以下简称同步器),是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作;
实现方式:继承重写方法
class Mutex implements Lock {
// 静态内部类,自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 是否处于占用状态
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 当状态为0的时候获取锁
@Override
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
} return false;
} // 释放锁,将状态设置为0
@Override
protected boolean tryRelease(int releases) {
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
} // 返回一个Condition,每个condition都包含了一个condition队列
Condition newCondition() { return new ConditionObject(); }
} // 仅需要将操作代理到Sync上即可
private final Sync sync = new Sync();
@Override
public void lock() { sync.acquire(1); }
@Override
public boolean tryLock() { return sync.tryAcquire(1); }
@Override
public void unlock() { sync.release(1); }
@Override
public Condition newCondition() { return sync.newCondition(); }
public boolean isLocked() { return sync.isHeldExclusively(); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
AQS实现分析
1.同步队列
2.独占式同步状态获取与释放
3.共享式同步状态获取与释放
4.独占式超时获取同步状态
3.重入锁
定义:重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞;
解决2个问题:
1)线程再次获取锁: 锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取;
2)锁的最终释放: 线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放;
//ReentrantLock的nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
4.读写锁 ReentrantReadWriteLock
5.Condition
实现原理
六 并发容器和框架
1.ConcurrentHashMap
HashMap并发环境的缺点
ConcurrentHashMap的优点
ConcurrentHashMap的结构,初始化方式,操作(get、put、size)
2.ConcurrentLinkedQueue
3.阻塞队列
4.Fork/join
MapReduce原理
七 13个原子操作类
java.util.concurrent.atomic
getAndIncrement实现原理:
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
八 并发工具类
CountDownLatch
CyclicBarrier
Semaphore
Exchanger:线程之间交换数据
九 线程池
①功能:1.降低资源消耗;2.提高相应速度;3.提高线程可管理性
②处理流程:
1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程;
2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程;
3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务;
③核心参数+参数含义
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
milliseconds,runnableTaskQueue, handler);
④任务提交线程池+关闭线程池
⑤线程池参数分配
⑥监控
十 Executor框架
1.四种ThreadPoolExecutor详解
2.FuntureTask
异步计算
当一个线程需要等待另一个线程把某个任务执行完后它才能继续执行,此时可以使用FutureTask。假设有多个线程执行若干任务,每个任务最多只能被执行一次。当多个线程试图同时执行同一个任务时,只允许一个线程执行任务,其他线程需要等待这个任务执行完后才能继续执行;
十一 并发编程实践
生产者消费者模式
线上问题的定位方法