https://www.yuque.com/docs/share/31deed7b-935a-4cf7-bcf3-8107bee40404?#0ea5e531
1. 线程基础
1.1 实现线程的方法
thread,runnable , callable
1.2 启动和停止线程
start
- stop()方法,它会释放已经锁定的所有监视资源,如果当前任何一个受监视资源保护的对象处于一个不一致的状态(执行了一部分),其他线程线程将会获取到修改了的部分值,这个时候就可能导致程序执行结果的不确定性,并且这种问题很难被定位。
- suspend()方法,容易发生死锁。因为调用suspend()方法不会释放锁,这就会导致此线程挂起。鉴于以上两种方法的不安全性,Java语言已经不建议使用以上两种方法来终止线程了。
- 一般建议采用的方法是让线程自行结束进入Dead状态。一个线程进入Dead状态,既执行完run()方法,也就是说提供一种能够自动让run()方法结束的方式,在实际中,我们可以通过flag标志来控制循环是否执行,从而使线程离开run方法终止线程。上述通过stop()方法虽然可以终止线程,但同样也存在问题;当线程处于阻塞状态时(sleep()被调用或wait()方法被调用或当被I/O阻塞时),上面介绍的方法就不可用了。此时使用interrupt()方法来打破阻塞的情况,当interrupt()方法被调用时,会跑出interruptedException异常,可以通过在run()方法中捕获这个异常来让线程安全退出
1
2
3
4
5
6
7
8
9public class MyThread implements Runnable{
private volatile Boolean flag;
public void stop(){
flag=false;
}
public void run(){
while(flag);//do something
}
}
1.3 线程 的六种状态
1 | NEW - 创建状态,线程创建之后,但是还未启动。 |
1.4 wait,notify,sleep,join等重要方法
wait 阻塞
notify 释放
sleep 睡眠
join 线程没执行完之前,会一直阻塞在join方法处
1.5 守护线程
2. 线程安全
2.1 各种各样的锁
悲观锁和乐观锁
共享锁和独占锁
公平锁和非公平锁
可重入锁和非可重入锁
JVM对synchronized锁的优化
synchronized深入理解
JVM synchronize锁
ReentrantLock原理解读
2.2 并发容器
ConcurrentHashMap
CopyOnWriteArrayList
阻塞队列 - BlockingQueue接口
非阻塞队列 - ConcurrentLinkedQueue
2.3 ThreadLocal
ThreadLocal是一个关于创建线程局部变量的类。使用场景如下所示:
- 实现单个线程单例以及单个线程上下文信息存储,比如交易id等。
- 实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例。
- 承载一些线程相关的数据,避免在方法中来回传递参数。
https://blog.csdn.net/weixin_44050144/article/details/113061884
https://www.jianshu.com/p/377bb840802f
- ThreadLocal 对数据的存储通过 Thread 的 ThreadLocalMap完成
- ThreadLocal本身作为key ,Entry包装了key和value
- Entry 继承自WeakReference ,没有Next,也没有链表
- 开发地址法处理hash冲突
- ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
- 显示的调用remove来避免内存泄漏
ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。
2.4 atomic包,6种原子类
2.5 线程池
避免存在大量Thread,重用线程池内部的线程.从而避免了线程的创建和销毁带来的性能开销,同时能有效控制线程池的最大并发数,避免大量线程因互相抢占系统资源而导致阻塞现象发生
Executors , ThreadPoolExecutor ,
1 | public ThreadPoolExecutor(int corePoolSize, // 1 |
- corePoolSize: 核心线程池大小 , 线程池里一直不会被销毁的线程数量
- maximumPoolSize: 最大线程池容量
- workQueue: 线程等待队列
- keepAliveTime: 非核心线程空闲时的存活时间,该参数只有在 线程数量 > corePoolSize情况下才有用
- handler: 饱和策略,假如线程池已满,并且没有空闲的线程,这个时候不再允许提交任务
1 | AbortPolicy:拒绝提交,直接抛出异常,也是默认的饱和策略; |
2.5.1 FixedThreadPool
适用场景:可用于Web服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。
1 | public static ExecutorService newFixedThreadPool(int nThreads) { |
- corePoolSize与maximumPoolSize相等,即其线程全为核心线程,是一个固定大小的线程池
- workQueue 为LinkedBlockingQueue(无界阻塞队列),队列最大值为Integer.MAX_VALUE。如果任务提交速度持续大余任务处理速度,会造成队列大量阻塞。因为队列很大,很有可能在拒绝策略前,内存溢出
2.5.2 CachedThreadPool
适用场景:快速处理大量耗时较短的任务
1 | public static ExecutorService newCachedThreadPool() { |
- corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制
- keepAliveTime = 60s,线程空闲60s后自动结束
- workQueue 为 SynchronousQueue 同步队列,这个队列类似于一个接力棒,入队出队必须同时传递,因为CachedThreadPool线程创建无限制,不会有队列等待,所以使用SynchronousQueue, 对其的操作必须是放和取交替完成的,典型的生产者-消费者模型,它不存储元素,每一次的插入必须要等另一个线程的移除操作完成。
2.5.3 SingleThreadExecutor
1 | public static ExecutorService newSingleThreadExecutor() { |
2.6 线程配合
CountDownLatch ,CyclicBarrier
2.7 Java内存模型
- 原子性 - 保证指令不会受到线程上下文切换的影响
- 可见性 - 保证指令不会受 cpu 缓存的影响
- 有序性 - 保证指令不会受 cpu 指令并行优化的影响
volatile 原理
volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
- 对 volatile 变量的写指令后会加入写屏障
- 对 volatile 变量的读指令前会加入读屏障
happens-before:
happens-before 规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下 happens-before 规则,JMM 并不能保证一个线程对共享变量的写,对于其它线程对该共享变量的读可见
2.8 cas 无锁编程
CAS即比较并替换,它是通过硬件来保证操作的原子性.去修改某个值,修改失败则重试.
CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果
volatile 仅仅保证了共享变量的可见性,让其它线程能够看到最新值,但不能解决指令交错问题(不能保证原子性)
无锁效率高:
- 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。
- 线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑道,线程想高 速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
CAS 体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思
- 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
- 但如果竞争激烈(写操作多),可以想到重试必然频繁发生,反而效率会受影响
2.9 AQS
AQS:AbstractQuenedSynchronizer抽象的队列式同步器。是除了java自带的synchronized关键字之外的锁机制。
AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
注意:AQS是自旋锁:在等待唤醒的时候,经常会使用自旋(while(!cas()))的方式,不停地尝试获取锁,直到被其他线程获取成功
并发编程(七)J.U.C之AQS原理、ReentrantLock原理
Java并发之AQS详解
02_可重入锁(递归锁)+LockSupport+AQS源码分析
Java 并发一步一脚印(专栏)
3. 生产者消费者
4. 线程通信
- synchronized加wait/notify方式
- ReentrantLock加Condition方式
- 闭锁方式、栅栏模式(CountDownLatch,CyclicBarrier)
- Semaphore 信号量
- join
- Volatile 共享变量