文章目录
一、线程
1.进程
- 操作系统分配资源的单元
- 运行中的程序
2.线程
- CPU调度的最小单位
- 一个具体的执行单元(任务)
3.多线程
- 就是一个进程内,允许有多个线程,同时执行
- 优点:提高了CPU的利用率,增强了程序的功能
- 缺点:对硬件要求高(CPU,内存,硬盘)
- 线程安全:多线程访问同一个共享资源
4.线程创建方式
- 继承Thread
- 实现Runnable接口
- 实现Callable接口,call()可以抛出异常,有返回值
- 线程池
5.线程状态
- 新建:当一个Thread类或者子类的对象被声明或创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被Start后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,**run()**方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
6.多线程安全问题
- eg:
- 卖票,取款,秒杀,抢购
- 解决:加锁,排队
7.守护线程
8.线程间通信
二、并发编程
1.并发和并行
- 并发:同一时间如何应对事情(一个CPU采用时间片执行多个任务)
- 并行:同时做多件事情(多个CPU执行多个任务)
- 多线程访问同一个共享资源,线程安全问题,因为现在CPU是多核的,可以同时执行多个线程
2.安全问题
- 性能
- 死锁
- 原子性,有序性,可见性
3.CPU内存模型的问题
- 1.CPU---------内存----------IO(硬盘)
- 三者之间读写速度有差别
- CPU提供了缓存
- 任务细化到线程,切换执行
- 2.CPU对我们指令代码的顺序进行优化(重排)
- 3.JMM内存模型
- 当多个处理器在运算任务
- 访问同一个内存区时
- 将可能会得到不同的缓存数据,
4.线程安全问题
- 可见性,有序性,原子性
- 可见性:
- 不同线程中,有一个缓存,缓存要操作的变量,
- 为了提高效率,会等所有操作完成后,在将数据写入内存中
- 当数据还未写入主内存时,其他线程对其不可见
- a++问题
- 有序性:
- 指程序按照代码的顺序执行,
- CPU会对我们的代码的执行顺序进行优化,
- 重新排列指令
- 原子性:
- 多线程在多核CPU中运行,线程切换会打破原子性
- 一行代码本不应该拆分成执行的,但是线程切换导致其他线程也会执行
- 最终导致问题的结果与预期值不一样
- 总结:
- 缓存导致可见性问题
- 代码优化导致有序性问题
- 线程切换导致原子性问题
5.volatile关键字
- volatile修饰的变量,在一个线程操作后,可以对其他线程立即可见
- volatile修饰的变量,禁止指令重排序(按照代码顺序执行)
- volatile不能解决原子性问题
6.如何保证原子性
- 同一时刻可以保证对共享变量的修改是互斥的就可以保证原子性了
- 加锁
- synchronized可以保证在一个线程执行时,其他线程不能操作共享数据
- 保证了原子性,可见性,有序性
- 原子类的原子性,是通过volatile+CAS实现原子操作的
7.ConcurrentHashMap
- hashMap是线程不安全的
- ConcurrentHashMap是线程安全的(jdk7前是分段锁,jdk8就开始Node锁)
- 放弃了分段锁(将某个段整体加锁)采用CAS原则+synchronized
- 在put()时,先判断添加的数据节点是不是第一个节点,
- 如果是,采用CAS原则(进行比较)加入数据到第一个节点,无需加锁
- 如果不是第一个节点,会用第一个节点作为锁,添加synchronized锁的机制 加锁 保证安全
- JUC包中提共了一下用于处理高并发线程安全的类,效率高
- Hashtable也是线程安全的
- 直接将put方法整个加锁,锁粒度大,效率低
- ConcurrentHashMap放弃分段锁的原因:
- 加入多个分段锁浪费内存空间
- 生产环境中,map在放入时竞争同一个锁的概率小,分段锁会造成长时间等待
- 为了GC的效率
8.CAS原则
- 比较并交换
- 采用的是乐观锁+自旋锁
- 引进三个操作数
- 内存值,预估值,更新值
- 我们先从内存中读出预估值,再将其和内存值进行判断,若值相同,将更新值赋给内存值,若值不相同,则什么都不做
- 这样做的话效率比加锁高,只需要判断是否要更新数据,就不会阻塞,所有线程都可同时访问
- 但是多线同时执行,且还需要自旋,会导致CPU消耗比较高
- CAS(大)缺点:ABA
- 在某个线程中,将内存值由A改成B,在由B改成A,在让预期值去判断时,预期值与内存值相同,误以为该变量未曾修改过,就会导致问题
- 解决方法:
- 可以添加版本号,每次修改后更新版本号,可以在通过版本号来判断值是否改过
9.锁的分类
- 不是不同的锁,只是锁的设计范式不同
- 乐观锁
- 采用CAS原则,更新数据时,进行判断而已,不加锁实现
- 悲观锁
- 采用加锁更新数据,加锁synchronized和Lock被称为悲观锁
- 公平锁
- 就是按照线程等待的顺序,一旦锁被释放,那么排在第一个位置的线程获得锁,执行
- 非公平锁
- 就是不用排队,锁释放后,谁先抢到谁就先执行
- 可重入锁
- 又名递归锁
- 当线程获得到外层方法锁对象时,依然可以获得内部同步方法的锁,可以进入到内部方法,可以避免死锁问题
- 读写锁
- ReadWriteLock
- ReentrantReadWriteLock
- 实体锁,
- 可以多个数据同时读,
- 写数据时不能同时
- 分段锁
- jdk8以前的 ConcurrentHashMap ,分段加锁
- 提高效率,并不是一种锁
- 自旋锁
- 不断重试去抢占CPU,不会阻塞线程,但是数量很多,就会消耗CPU
- 共享锁
- 多个线程可以共享一把锁,就是读写锁的读锁
- 独占锁
- 一次只能有一个线程持有该锁
- AQS(抽象队列同步器)
- 没有
10. 锁的状态
- 无锁状态
- 偏向锁状态
- 当一直只有一个线程,一直获得取锁对象,赤水对象头中的锁状态就会改为偏向锁,并记录线程id,
- 同一个线程访问时,可以直接获取锁,效率高
- 轻量级锁状态
- 当第二个线程访问时,偏向锁就会升级为轻量锁
- 其他线程自旋,不会阻塞,提高效率(线程数量较少)
- 重量级锁状态
- 当前锁状态为轻量级别锁时,并发访问量增多
- 锁状态升级为重量级
- 其他线程进入到阻塞状态,不在自旋
三、ThreadLocal线程变量
1.线程封闭
- 对象封闭在一个线程中,即使这个对象不是线程安全,也不会出现并发问题
- 使用ThreadLocal来实现线程封闭,实现是封闭,不是共享
- 所以他可以解决线程安全问题,但不能解决线程共享问题
2.ThreadLocal是什么
- ThreadLocal就是线程变量
- 该线程的变量属于当前线程,该变量对其他线程是隔离的,
- ThreadLocal为变量提供一个副本,仅供其他线程访问自己内部的副本变量,使其只读
3.ThreadLocal原理分析
- 底层使用了一个ThreadLocalMap对象存储
- 将最终变量放在上述,仅供其他线程访问
4.ThreadLocal内存泄漏问题
- ThreadLocalMap使用ThreadLocal的弱引用作为key
- 如果一个ThreadLocal不存在外部强化引用时,key就会被GC(垃圾回收算法)回收,就会导致ThreadLocalMap中的key为null
- 而value还存在着强引用,这样thead线程退出以后,value的强引用链条才会断掉
- 建议,在使用完后,主动调用remove()删除
四、线程池
1.概述
- 因为线程的创建和销毁都需要时间,而频繁的创建和销毁更加消耗时间
- 则可以使用线程池,预先创建一些线程,
- 当一条线程运行结束后,让其回到线程池中,变成空闲状态,等待下一个对象来使用
- 这样就可以减少频繁创建和销毁线程开销
2. ThreadPoolExecutor
- 7个参数
- corePoolSize:核心线程数量(不会被销毁)
- 1.prestartAllCoreThreads()或许prestartCoreThread()方法,预先创建核心数量个线程和预先创建一个线程
- 2.起初不创建,有任务时直到达到核心数量个线程
- maximumPoolSize:最大线程数量
- keepAliveTime: 超出核心线程数量部分的线程,在么有任务执行时,空闲多久后销毁
- unit:keepAliveTime的时间单位
- workQueue:指定等待的阻塞队列
- threadFactory:线程工厂,主要用来创建线程
- handler:表示但拒绝处理任务时的策略
3.线程池的执行
- 如果核心线程池未满,就会创建一个核心线程去提交任务
- 如果线程池以满就会提交一个新任务,将新任务放在阻塞队列中等待
- 如果阻塞队列也满了,就会判断最大线程是否已满,若尾满Juin创建一个非核心线程来提交任务
- 如果最大线程已满,还有新的任务过来,就会直接采用拒绝策略处理
4.线程池的阻塞队列
- ArrayBlockingQueue:有界队列,使用一个数组实现的
- LinkedBlockingQueue:可设置容量队列,如果不设置的话就会默认一个无边界的阻塞队列,最大长度为Integer-8
5.线程池的拒绝策略
- AbortPolicy 策略:该策略会直接抛出异常,阻止系统正常工作。
- CallerRunsPolicy 策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。
- DiscardOleddestPolicy 策略:该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
- DiscardPolicy 策略:该策略默默的丢弃无法处理的任务,不予任何处理。
6.execute 与 submit 的区别
- execute无返回值
- submit有返回值
7.关闭线程池
- shutdownNow 和 shutdown 两个方法
- shutdownNow
- 对正在在执行的任务全部发出interrupt(),停止执行,对还未开始的任务全部取消,并且返回开没开始的任务列表
- shutdown
- 调用后,线程池不咋接受新的任务,但会完成正在运行的线程,直到他们销毁.