目录
第一章 走入并行世界
概念
1. 同步/异步:同步方法调用者需等待方法调用完成后,才能执行后续行为;异步反之
2. 并发/并行:并发偏重于多个任务的交替执行,而多个任务可能是串行的;并行是真正意义上多个任务的“同时执行”
3. 临界区:公共资源/共享资源
4. 阻塞和非阻塞:当前线程被其他线程占用临界区资源而等待
5. 死锁
6. 饥饿:线程无法获得资源,导致一直无法执行
7. 活锁:相互“谦让”,共享资源在线程间跳动,没有一个线程同时拿到所有资源而执行
8. 【并发级别】★(5个):
* 阻塞(Blocking):有序进行
* 无饥饿(Starvation-Free):线程调度倾向于高优先级线程 (公平与非公平锁)
* 无障碍(Obstruction-Free):(最弱的非阻塞调度)两个线程不会因为临界区导致挂起,都去操作临界区资源(乐观锁与悲观锁)
* 无锁(Lock-Free):无锁并行都是无障碍的。无锁情况下,所有线程都可访问临界区,不同的是,无锁的并发保证必然有一个线程能在有限步完成操作离开临界区
* 无等待(Wait-Free):无锁只要求一个线程在有限步完成,无等待要求所有线程必须在有限步内完成,这样不会引起饥饿问题
图解:
9.Java内存模型:JMM
特点:多线程的原子性、可见性和有序性
原子性:一个操作不可中断,即使在多个线程一起执行的时候,一个操作一旦开始就不会被其他线程干扰;
可见性:当一个线程修改了某一个共享变量值,其他线程是否能够立即知道这个修改;
有序性:指令执行顺序
指令重排可提高CPU执行效率,有些不满足指令重排规则(Happen-Before原则):
程序顺序原则:一个线程内保证语义的串行性
volatile原则:volatile变量写在读之前,也保证了volatile变量的可见性
锁规则:解锁必然发生在加锁前
传递性:A先于B,B先于C,那么A先于C
线程的start()方法先于它的每一个动作
线程的所有操作先于线程的终结(Thread.join())
线程中断(interrupt())先于被中断线程的代码
对象的构造函数执行、结束先于finalize()方法
第二章 Java并行程序基础
线程状态Enum:public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITTING,
TIMED_WAITTING,
TERMINATED;
}
基本操作
1.新建
通过继承Thread类本身
通过实现Runnable接口
通过Callable和Future创建线程
2.终止
不推荐使用 stop() 方法
推荐使用标志位 flag 校验结束线程
public class CreateThread extends Thread{
CreateThread u = new CreateThread();
volatile boolean stopme = false;
public void stopMe() {
stopme = true;
}
@Override
public void run() {
while(true) {
if (stopme == true) {
System.out.println("stop!");
break;
}
synchronized (u) {
//业务逻辑代码
}
}
}
}
3.中断
public void Thread.interrupt() //中断线程
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
可以通过判断当前线程是否被中断来做处理,类似于stopMe标志位,但是在wait() 和 sleep() 这些操作中,就只能用中断标志来完成
【注意】Thread.sleep() 方法 + 中断会触发 InterruptedExecption 异常,抛完异常并会清除中断标记
4.wait和notify
Object.notify 和 notifyAll 方法区别
Object.wait() 方法需要包含在synchronize 语句中
wait和sleep区别:wait方法会释放对象锁,sleep不会
5.挂起(suspend)和继续执行(resume) (废弃)
6.等待结束(join)和谦让(yield)
主线程Main会等待at线程执行完毕,不加join的话 i 不会打印到10000000;
join的核心代码:思想就是让调用线程wait在当前线程对象实例上等着
while(isAlive) {
wait(0);
}
//yield() 是个静态方法,一旦执行,会使当前线程让出CPU,让出CPU不代表当前线程不执行。让完还会进行CPU资源争夺
public static native void yield();
volatile关键字
不保证原子性
第6行是原子性,然而结果总是小于100000!
守护线程(Daemon)
哪些是守护线程?垃圾回收线程,JIT线程
线程优先级
等级:1-10
synchronize 关键字
* 指定加锁对象:对给定对象加锁,进入同步代码前获得给定对象的锁;
* 作用于实例方法:相当于对当前实例加锁,进入同步块前要获得当前实例的锁;
* 作用于静态方法:相当于对当前类加锁,进入同步代码块前要获得当前类的锁;
//加锁对象
class SynClass implements Runnable {
SynClass syncSlass = new SynClass();
static int i = 0;
@Override
public void run() {
synchronized (syncSlass) {
i++;
}
}
}
//加锁方法
class SynClass implements Runnable {
SynClass syncSlass = new SynClass();
static int i = 0;
public synchronized void increase() {
i++;
}
@Override
public void run() {
increase();
}
}
并发环境下的常见问题?
ArrayList会出现扩容异常
HashMap扩容链结构发生改变,成环遍历的话导致程序一直执行 【可通过jps和jstack打印排查】
第三章 JDK并发包(JUC)
重入锁 ReentrantLock
作用:
1.中断响应:请求锁的时候要么获得锁执行,要么等待;中断响应可以自己根据需要取消请求;
2.锁申请等待延时:限时等待,避免死锁
3.公平锁
public ReentrantLock(boolean fair); //默认是非公平锁 = false
Condition条件
与ReentrantLock结合使用,类似于interrupt 和 signal 方法
信号量(Semaphore)
同时允许有限个线程同时访问同一个临界区
ReadWriteLock 读写锁
private static ReantrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock .readLock();
private static Lock writeLock = readWriteLock.writeLock();
CountDownLatch
public class CountDownLatchDemo implements Runnable {
static final CountDownLatch end = new CountDownLatch(10);//计数数量10个
static final CountDownLatchDemo demo = new CountDownLatchDemo();
@SneakyThrows
@Override
public void run() {
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println("check complete!");
end.countDown();//倒计时器减一
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.submit(demo);
}
end.await();//要求主线程等待10个检查任务全部完成之后,主线程才能继续执行
System.out.println("Fire");
executorService.shutdown();
}
}
CyclicBarrier
用例(待补充)
线程阻塞工具类 LockSupport(了解)
线程池
线程池种类与区别
//1.固定大小,每批次线程执行个数固定为5个
final ExecutorService executorService = Executors.newFixedThreadPool(5);
//2.计划任务,返回一个ScheduledExecutorService对象,起到计划任务作用;
// 可以在指定时间,对任务进行调度;
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
// 2.1 任务执行5秒,调度周期为2秒。也就是每2秒,任务执行一次,一次执行5秒;
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(5000);
}
},0,2, TimeUnit.SECONDS);
// 2.2 任务执行5秒,每隔任务之间间隔等待2秒
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(5000);
}
},0,2,TimeUnit.SECONDS);
线程池内部实现
不管是哪种线程池,内部都是由 ThreadPoolExecutor 类实现的;
重点掌握其中的workQueue和handler两项:
workQueue分类:
1.直接提交队列:SynchronousQueue
2.有界任务队列:ArrayBlockingQueue
3.无界任务队列:LinkedBlockingQueue
4.优先任务队列:PriorityBlockingQueue
核心调度源码实现
拒绝策略
自定义拒绝策略
自定义线程池创建(ThreadFactory)
拓展线程池
**【调优】**线程池大小设置多少合适?
Fork/Join 框架
MapReduce原理:归并思想
工具类(并发集合)源码实现
ConcurrentHashMap:1.7/1.8优化★
CopyOnWriteArrayList:修改时修改的是副本
ConcurrentLinkedQueue:offer方法使用CAS,实现过程★
BlockingQueue:数据共享通道,put和take方法的交互通过Condition进行交互★
ConcurentSkipListMap;跳表结构★
跳表:类似于平衡树,可以实现快速查找;相较于平衡树而言,平衡树在插入和删除元素会触发全局调整,而跳表只需要部分锁即可,高并发下性能好;
【结构】最底层是链表所有元素,所有元素是排序的,上一层是下一层子集,从上一层向下一层去找插入位置,最终落地至最后一层即可;
【思想】空间换时间,如下是找7的过程:
【比较】与HashMap不同,跳表的输出是有序的;
第四章 锁优化与注意事项
锁性能优化点
1.减少锁持有时间:只锁住共享的最小范围的方法即可;示例:JDK中Pattern类中实现
2.减小锁粒度:ConcurrentHashMap的put和get方法
3.读写分离锁替换占用锁
4.锁分离
5.锁粗化:减少不必要的细粒度的锁
JVM锁优化
1.锁偏向
2.轻量级锁
3.自旋锁
4.锁消除
锁消除是一种更彻底的锁优化。JVM在JIT编译时,通过对上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁消除,可以节省毫无意义的请求锁时间;
ThreadLocal 线程局部变量
1.使用:简单容器作用
2.核心源码实现:set和get方法
3.清理回收过程
值的存取是在线程的ThreadLocalMap中,维护也是在Thread类内部的,意味着线程不退出,对象引用一直存在;
证明:在Thread类清理工作中,就包含了对ThreadLocalMap的清理
ThreadLocal缺点:不适合用于存大对象,会造成内存泄漏,应为不用的时候不会被自动清理;对象不用可以使用ThreadLocal.remove()方法移除对象;
无锁 CAS
CAS 算法过程:CAS(V,E,N),V-要更新的变量;E-预期值;N-新值;
只有当V=E的时候,才会把V设为N;
AutomicInteger
CAS 核心源码实现:
拓展:
Java中指针:Unsafe类(了解)
无锁对象引用:AutomicReference(了解)
带时间戳的对象引用:AtomicStampedReference(了解)
无锁数组:AutomicIntegerArray(了解)
死锁
第五章 并行模式与算法
单例模式(四种实现)
1.懒汉式
2.饿汉式
3.静态内部类
4.双重校验锁
生产者-消费者模式
使用BlockingQueue实现生产者-消费者模型是利用锁和阻塞等待实现的线程之间的同步;
替代实现为ConcurrentLinkedQueue,大量使用CAS操作;
Future模式
异步调用
并行排序算法
NIO
利用NIO改造Socket实现通信
第六章 Java8与并发
Scalar(了解)
函数式编程
原子类增强 LongAdder: 原理就是将内部核心数据 value 分离为一个数组,每个线程访问时,通过哈希等算法映射到某一个数字进行计数,得到最终结果
第七章 使用Akka构建高并发程序
Akka模式