并发
基础
进程与线程
并发与并行
- 并发:两个及两个以上的作业在同一 时间段内执行
- 并行:两个及两个以上的作业在同一 时刻 执行
多线程问题
-
内存泄漏、
-
死锁
-
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
-
产生条件
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
-
如何预防
- 破坏请求与保持条件 :一次性申请所有的资源。
- 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件
-
-
线程不安全
线程的生命周期
-
new
- 初始状态,线程被构建,但是还没有调用startO方法
-
RUNNABLE
- 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行
-
BLOCKED
- 阻塞状态,阻塞状态,表示线程阻塞于锁
-
WAITING
- 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
-
TIME WAITING
- 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
-
TERMINATED
- run方法执行后,终止状态,表示当前线程已经执行完毕
线程间的通信方式
-
共享内存
-
消息传递
- 比如#wait() 和 #notify()
上下文切换
- 主动让出 CPU,比如调用了 sleep(), wait() 等。
- 时间片用完,因为操作系统要防止一个线程或者进程长时间占用CPU导致其他线程或者进程饿死
- 调用了阻塞类型的系统中断,比如请求 IO,线程被阻塞
- 被终止或结束运行
sleep和wait方法的区别
- sleep方法没有释放锁,wait方法释放了锁
- wait() 通常被用于线程间交互/通信,sleep() 通常被用于暂停执行
- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout) 超时后线程会自动苏醒。
start方法和run方法
- 调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作
进阶
线程安全
-
synchronized 关键字
-
使用
-
方法
- 修饰实例方法
- 修饰静态方法
-
修饰代码块
-
-
底层原理
- 1.synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
- 2.synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。
-
优化
-
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
-
四种状态
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态
-
-
和 ReentrantLock 的区别
-
都是可重入锁
-
synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
-
ReentrantLock 比 synchronized 增加了一些高级功能
- 等待可中断
- 可实现公平锁,synchronized只能是非公平锁
- 可实现选择性通知(锁可以绑定多个条件)
-
-
和volatile关键字的区别
- 是两个互补的存在,而不是对立的存在
- volatile 关键字是线程同步的轻量级实现,性能肯定比synchronized关键字要好
- volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
- volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。
-
-
volatile 关键字
-
CPU 缓存模型(L1,L2,L3 Cache)
- 内存
- CPU Cache 缓存
- CPU寄存器
- CPU 为了解决内存缓存不一致性问题可以通过制定缓存一致协议或者其他手段来解决。
-
JMM(Java 内存模型)
-
主内存
- 所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)
-
本地内存
- 每个线程都有一个私有的本地内存来存储共享变量的副本,并且,每个线程只能访问自己的本地内存,无法访问其他线程的本地内存。本地内存是 JMM 抽象出来的一个概念,存储了主内存中的共享变量副本。
-
-
并发编程的三个重要特性
- 原子性
- 可见性
- 有序性
-
-
ThreadLocal
-
每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。
-
ThreadLocalMap是ThreadLocal的静态内部类。
-
内存泄露问题
- key 为 ThreadLocal 的弱引用,而 value 是强引用
-
-
线程池
-
降低资源消耗、提高响应速度、提高线程的可管理性
-
Runnable 接口和 Callable 接口的区别
- 不需要返回结果或抛出异常推荐使用 Runnable 接口
-
execute()方法和 submit()方法的区别
- execute()方法用于提交不需要返回值的任务
- submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象
-
创建方式
-
《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式
-
Executors 返回线程池对象的弊端
- FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。
- CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
-
-
ThreadPoolExecutor 创建方式
-
通过构造方法实现
-
通过 Executor 框架的工具类 Executors 来实现
- FixedThreadPool
- SingleThreadExecutor
- CachedThreadPool
-
ThreadPoolExecutor参数
-
corePoolSize : 核心线程数定义了最小可以同时运行的线程数量。
-
maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数
-
workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中
-
keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁
-
unit :keepAliveTime 参数的时间单位
-
threadFactory :executor 创建新线程的时候会用到。
-
handler :饱和策略。
- ThreadPoolExecutor.AbortPolicy抛出 RejectedExecutionException来拒绝新任务的处理。(默认)
- ThreadPoolExecutor.CallerRunsPolicy直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。
- ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。
- ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求
-
-
-
-
运行原理
- 任务来,创建核心线程,接着放着工作队列 ,队列满了之后创建工作线程直到达到最大线程数,之后执行拒绝策略
-
-
Atomic 原子类
-
原子类类型
-
基本类型
- AtomicInteger:整型原子类
- AtomicLong:长整型原子类
- AtomicBoolean:布尔型原子类
-
数组类型
- AtomicIntegerArray:整型数组原子类
- AtomicLongArray:长整型数组原子类
- AtomicReferenceArray:引用类型数组原子类
-
引用类型
- AtomicReference:引用类型原子类
- AtomicStampedReference:原子更新带有版本号的引用类型。
- AtomicMarkableReference :原子更新带有标记位的引用类型
-
对象的属性修改类型
- AtomicIntegerFieldUpdater:原子更新整型字段的更新器
- AtomicLongFieldUpdater:原子更新长整型字段的更新器
- AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器
-
-
AtomicInteger 类的原理
- 主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
- CAS 的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个 volatile 变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
-
-
AQS(AbstractQueuedSynchronizer)
-
概念
- AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出大量应用广泛的同步器,比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask 等等皆是基于 AQS 的
-
原理
- 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
- CLH(Craig,Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配
-
AQS 对资源的共享方式
-
Exclusive(独占)
- ReentrantLock
-
Share(共享)
- 如 CountDownLatch、Semaphore、 CyclicBarrier、ReadWriteLock
-
-
底层使用了模板方法模式
-
AQS 组件总结
-
Semaphore(信号量)-允许多个线程同时访问
-
synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
- 银行窗口办业务
-
-
CountDownLatch (倒计时器)
-
是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。
- 计数器
-
-
CyclicBarrier(循环栅栏)
-
和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。
- 计数器循环使用
-
-
-