前言:本篇文章主要讲解java并发编程的知识。
目录
一、线程有关的基本概念
1.1线程安全
1.1.1线程安全的基本概念
1.1.2线程安全等级
1.2线程的同步异步、阻塞非阻塞
1.2.1区分同步异步、阻塞非阻塞
1.2.2同步异步使用场景
1.2.3分清同步异步的优势劣势
1.2.4阻塞
1.2.5非阻塞
1.3线程的并发和并行
1.4线程状态及java中线程常见方法
1.4.1线程的状态
1.4.2状态切换
1.4.3常见Thread方法用途和示例
1.5线程死锁和避免
1.5.1线程死锁的发生原因
1.5.2发现线程死锁的方法
1.5.3如何避免代码出现线程死锁
二、java内存模型及线程案例分析
2.1java内存模型
2.1.1cpu的内存模型
2.1.2java的内存模型
2.1.3熟悉指令重排和Happens-before规则
2.2synchronized和volatile关键字
2.3创建线程的几种方式
2.4ThreadLocal的定义和使用
2.5ThreadLocal的实现原理
三、线程池原理及应用
3.1线程池的创建和常用参数分析
3.1.1创建线程池的参数解义
3.1.2线程池状态分析
3.2常用线程池
3.2.1Java SDk的常用线程池
3.2.2线程池执行过程分析
3.3线程池常用列队值LinkedBlockingQueue
3.3.1LinkedBolockingQueue的数据结构
3.3.2LinkedBolockingQueue的实现原理
3.4可定时执行的线程池原理分析
3.4.1ScheduledExecutorService的使用场景
3.4.2DelayedWorkQueue的数据结构和实现原理
3.4.3ScheduledExcutorService的实现原理
3.5线程池的同步异步调用Callable,Future
四、java锁及应用
4.1乐观锁CAS实现及应用
4.1.1乐观锁悲观锁的区别
4.1.2java提供的乐观锁实现
4.2数据库悲观锁乐观锁实现
4.2.1数据库悲观锁乐观锁的实现和区别
4.2.2数据库悲观锁乐观锁的应用场景
4.3AQS的数据结构
4.3.1AQS是什么
4.3.2AQS的数据结构
4.4ReentrantLoack的加锁解锁
4.4.1ReentrantLock简单加锁解锁过程
4.4.2ReentranLocak怎么实现可重入
4.4.3AQS加锁排列等待的实现
4.4.4公平锁和非公平锁的实现区别
五、并发容器及原理分析
5.1kv集合HashMap的实现原理
5.2HashMap在高并发场景下死循环分析
5.3ConcurrentHashMap如何解决高并发下的问题
5.4CopyOnWriteArrayList如何做到线程安全
线程安全:一个类被多个线程以任意方式同时调用,且不需要外部额外同步和协同的情况下,仍然保持内部数据正确且表现正确的行为,那么这个类就是线程安全的。
线程安全等级:不可变、线程安全、有条件的线程安全、线程兼容、线程对立
a、不可变的对象一定是线程安全的
举例:final修饰的不可变类,如String、Integer,enum枚举类
b、线程安全
线程安全类的任意方法操作都不会使该对象的数据处于不一致或者数据污染的情况
例如 ConcurrentHashMap、LinkedBlockingQueue
c、有条件的线程安全
对于单独的访问类的方法,是线程安全但是对于某些复合操作,需要外部类来同步
举例 Vector中的contains和add同时使用时是不安全的,组合使用时使用synchronized修饰
d、线程兼容类
使用synchronized控制同步访问每一个代码块或者类
举例 Collections.synchronizedList 来包装一个List,使其变为线程安全
e、线程对立类
不管是否调用了外部同步都不能在并发使用时保证其安全的类
举例 多线程买票
同步的使用场景
a.大多数非异步场景(不用异步,就用同步来调用)
如百度搜索,客户端同步调用服务器搜索接口,等待服务器实时结构
b.在编排的流程中,必须等待拿到响应结果才能去做下一步操作,且在实时链路中相互之间有数据串联或关联数据的
如电商中商品详情页的查询接口的内部实现
异步
非阻塞式调用,立即返回,调用方无需等待响应方返回实际结果,响应方会通过状态、通知或回调告知调用方
1.耗时任务。主线程中提交耗时任务到线程池,然后通过Future来异步获取任务执行结果,这里也可以由异步任务发消息等途径来通知主线程
2.业务链路太长,可以把非核心的部分用异步实现
同步优点:
1.可以拿到实时结果进行处理,上下问信息始终在一个代码块,代码处理上更加方便直观
2.对错误和异常处理可以做到实时
同步缺点:
1.耗时的接口响应会影响整个流程的性能
异步优点:
1.不影响主线程的执行,降低响应时间,提高应用的性能和效率
2.及时释放系统资源,如线程占用,让系统去做更有价值的事
异步缺点:
1.为了保障数据最终一致性,需要对账系统去做好监控和风险
2.需要更多异步任务去补偿系统间的数据一致性
调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回
非阻塞调用指在不能立刻得到结果之前,该调用不会则赛当前线程,而会立即返回。
并发指宏观上,逻辑上单个cpu轮流处理
并行指物理上,多个cpu在同时处理
在java.lang.Thread中的内部枚举State中可见
Thread.yield()
线程让步,使用了这个方法,当前线程就会推出cpu时间片,让其他线程或当前线程使用cpu时间片执行
Thread.sleep()
线程休眠,主动让出当前cpu时间,在指定时间过后,cpu会返回继续执行该线程,sleep方法不会释放当前所持有的锁
Thread.join()
等待该线程死亡/终止,当前线程会等待调用该方法的线程执行完毕后才能继续执行
Object.wait()和Object.notifyAll()
wait()方法调用前必须拥有对象锁,例如在synchronized代码块内,调用wait方法后,对象锁会释放,
线程进入WAITTING等待状态
jstack [java进程pid] 找出死锁地方
避免获取锁顺序不一致
L1和L2是每个cpu自己的高速缓存
L3是多cpu之间共享的缓存
memory是主存
每个线程都有自己的工作内存
工作内存包含线程本地局部变量和主内存的副本拷贝
线程之间的共享变量通过主内存在各线程间同步
java源代码会经过编译重排,处理器重排等过程,Happens-before是保证代码能先后执行的一种规则
synchronized可以修饰方法和代码块
修饰方法普通方法时锁对象是类实例出来的对象
修饰静态方法时锁对象是类本身
ps:javap是反编译工具
volatile修饰的属性是强调可见性
a.new Thread(runnable).start()
b.new MyThread().start()
c.new Thread(new FutureTask(callable)).start()
d.Excutors框架
new ThreadLocal<>(),注意remove,不然有oom(outofmemory)
实现原理为ThreadLocalMap,其中entry中的key就是Thread ,value就是我们储存的数据对象
下面是ThreadPoolExecutor的构造方法参数
a.corePoolSize 核心线程数,保持在线程池中线程的数量
b.maximumPoolSize 线程池允许的最大线程数
c.keepAliveTime/timeUnit 线程池中线程空闲不被释放的最大时间,配合timeUnit使用,为0表示永远不被释放
d.workQueue BlockingQueue
e.threadFactory 线程池创建工厂,子类通过自定义实现接口Thread newThread(Runnable) 通过工厂创建线程池具体的Thread线程 默认实现: DefaultThreadFactory
f.handler(RejectedExecutionHandler)
当workQueue无法存放新加任务,或添加人物后线程池停止工作,使用设置的拒绝策略拒绝新加任务的执行,可以用rejectedExecution来实现自己的拒绝策略
默认拒绝策略;AbortPolicy,直接抛出异常
CallerRunsPolicy:调用方执行策略,当前调用线程或添加任务的线程执行
DiscardPolicy:直接抛弃策略,对任务不做任何事情,忽略该任务
DiscardOldestPolicy 抛弃最早任务策略,将workQueue的一个任务取出抛弃,经当前任务放入workQueue中执行
在ThreadPoolExecutor类中有相应的列举
a.固定线程数量的线程池
Executors.newFixedThreadPool()
b.单线程的线程池
Executors.newSingleThreadPool()
c.可缓存的线程池
Executors.newCachedThreadPool()
.这里使用同步列队
d.定时执行的线程池
Executors.newScheduledThreadPool()
使用了DelayedWorkQueue延时列队,通过延时队列来控制时间来执行
看源码,入口为ThreadPoolExcutor中的execute(Runnable command)
添加元素
取出元素
1.定时执行异步任务
2.周期执行异步任务
最小堆结构,以距离当前时间的值作为排序依据
主要依据的是DelayedWorkQueue这个特殊的列队实现的,spring中@Scheduled注解本质上就是ScheduledExcutorService
future是通过执行callable中的call方法,而call方法可能是一个耗时方法,把call执行的返回值通过共享变量的方式(FutureTask)在执行任务的线程与获取Future的主线程中通信
为什么加锁: 为了保证多个线程更新一个资源时,防止数据冲突和脏乱,做到线程安全
CAS解释
全名:compare and swap,先比较然后设置,CAS是一个原子操作(基于操作系统,JNI的方式实现)
适用场景
更新一个值,不依赖于加锁实现,可以接收CAS失败
局限
只可以更新一个值,如AtomicReference、AtomicInteger需要同时更新时,无法做到原子性
cas可能会出现ABA的问题
select ...lock in share mode 共享锁
select ...for update 排他锁
update set ... version = vesion+1 where version = $version$
cas思路,使用version版本控制
悲观锁的应用场景
并发不是很高,或者要求效率很高的情况下
AbstractQueuedSynchronizer
提供一个框架来实现阻塞所和相关的依赖于先进先出等待队列
各种同步组件的核心抽象实现类
管理等待队列,锁的占用和释放,中断、超时和通知等
具体看AQS类的源码即可
获取锁的时候
释放锁的时候
非公平锁lock的时候,及时有等待的列队,他也会cas操作尝试获取锁
公平锁的实现在获取锁的时候先判断双向列队是否有在等待的线程
各jdk版本不太相同,大致都是数组加链表的结构,jdk1.7是数据加单向链表 jdk1.8是数组加红黑树结构,后来的加在最后面
jdk1.7出现死循环分析
jdk1.7中ConcurrentHashMap的数据结构,注意Segment是继承了ReentrantLock的,整体上是分段锁的思想
jdk1.8中已经去掉分段锁了,直接使用synchronized关键字了。下图是jdk1.7的数据结构图