线程与并发

简介:

ThreadLocal 的原理
ThreadLocal 的主要目的是用来实现多线程环境下的变量隔离
● 【解释】即每个线程自己用自己的资源,这样就不会出现共享,没有共享,就不会有多线程竞争的问题
原理
● 每个线程对象内部有一个 ThreadLocalMap,它用来存储这些需要线程隔离的资源
● 资源的种类有很多,比如说数据库连接对象、比如说用来判断身份的用户对象 ...
● 怎么区分它们呢,就是通过 ThreadLocal,它作为 ThreadLocalMap 的 key,而真正要线程隔离的资源作为 ThreadLocalMap 的 value
○ ThreadLocal.set 就是把 ThreadLocal 自己作为 key,隔离资源作为值,存入当前线程的 ThreadLocalMap
○ ThreadLocal.get 就是把 ThreadLocal 自己作为 key,到当前线程的 ThreadLocalMap 中去查找隔离资源
● ThreadLocal 一定要记得用完之后调用 remove() 清空资源,避免内存泄漏

5.2 解释悲观锁与乐观锁
悲观锁
● 像 synchronized,Lock 这些都属于悲观锁
● 如果发生了竞争,失败的线程会进入阻塞
● 【理解】悲观的名字由来:害怕其他线程来同时修改共享资源,因此用互斥锁让同一时刻只能有一个线程来占用共享资源
乐观锁
● 像 AtomicInteger,AtomicReference 等原子类,这些都属于乐观锁
● 如果发生了竞争,失败的线程不会阻塞,仍然会重试
● 【理解】乐观的名字由来:不怕其他线程来同时修改共享资源,事实上它根本不加锁,所有线程都可以去修改共享资源,只不过并发时只有一个线程能成功,其它线程发现自己失败了,就去重试,直至成功
适用场景
● 如果竞争少,能很快占有共享资源,适合使用乐观锁
● 如果竞争多,线程对共享资源的独占时间长,适合使用悲观锁
P.S.
● 这里讨论 Java 中的悲观锁和乐观锁,其它领域如数据库也有这俩概念,当然思想是类似的

5.3 synchronized 原理
以重量级锁为例,比如 T0、T1 两个线程同时执行加锁代码,已经出现了竞争(代码如下)
synchronized(obj) { // 加锁
...
} // 解锁

  1. 当执行到行1 的代码时,会根据 obj 的对象头找到或创建此对象对应的 Monitor 对象(C++对象)
  2. 检查 Monitor 对象的 owner 属性,用 Cas 操作去设置 owner 为当前线程,Cas 是原子操作,只能有一个线程能成功
    a. 假设 T0 Cas 成功,那么 T0 就加锁成功,可以继续执行 synchronized 代码块内的部分
    b. T1 这边 Cas 失败,会自旋若干次,重新尝试加锁,如果
    ⅰ. 重试过程中 T0 释放了锁,则 T1 不必阻塞,加锁成功
    ⅱ. 重试时 T0 仍持有锁,则 T1 会进入 Monitor 的等待队列阻塞,将来 T0 解锁后会唤醒它恢复运行(去重新抢锁)
    5.4【追问】 synchronized 锁升级
    synchronized 锁有三个级别:偏向锁、轻量级锁、重量级锁,性能从左到右逐渐降低
    ● 如果就一个线程对同一对象加锁,此时就用偏向锁
    ● 又来一个线程,与前一个线程交替为对象加锁,但只是交替,没有竞争,此时要升级为轻量级锁
    ● 如果多个线程加锁时发生了竞争,必须升级为重量级锁
    【说明】
    ● 自 java 6 开始对 synchronized 提供了锁升级功能,之前只有重量级锁
    ● 但从 java 15 开始,偏向锁被标记为已废弃,将来会移除(因为实际带来的性能提升不明显,某些情况下反而影响性能)

5.5 对比 synchronized 和 volatile
并发编程需要从三个方面考虑线程安全,分别是:原子性、可见性、有序性
● volatile 修饰共享变量,可以保证它的可见性和有序性,但不能保证原子性(JMM模型)
● synchronized 代码块,不仅能保证共享变量的可见性、有序性,同时也能保证原子性
P.S.
● 实际上用 volatile 去保证可见性和有序性,并不像上面那一句话描述的那么简单,可以参考黑马课程

5.6 对比 synchronized 和 Lock
● synchronized 是关键字,Lock 是 Java 接口
● 前者底层是 C++ 代码实现锁,后者是 Java 自己的代码来实现锁
● Lock 功能更多,比如可以选择是公平锁还是非公平锁、可以设置加锁超时时间、可打断等
● Lock 的提供多种扩展实现(例如读写锁),可以根据场景选择更合适的实现
● Lock 释放锁需要调用 unlock 方法,而 synchronzied 在代码块结束无需显式调用就可以释放锁

5.7 线程池的核心参数
记忆七个参数

  1. 核心线程数
    a. 核心线程会常驻线程池
  2. 最大线程数
    a. 如果同时执行的任务数超过了核心线程数,且队列已满,会创建新的线程来救急
    b. 总线程数(新线程+原有的核心线程)不超这个最大线程数
  3. 存活时间
    a. 超过核心线程数的线程一旦闲下来,会存活一段时间,然后被销毁
  4. 存活时间单位
  5. 工作队列
    a. 如果同时执行的任务数超过了核心线程数,会把暂时无法处理的任务放入此队列
  6. 线程工厂
    a. 可以控制池中线程的命名规则,是否是守护线程等(不太重要的参数)
  7. 拒绝策略,队列放满任务,且所有线程都被占用,再来新任务,就会有问题,此时有四种拒绝策略:
    a. AbortPolicy 报错策略,直接抛异常
    b. CallerRunsPolicy 推脱策略,线程池不执行任务,推脱给任务提交线程
    c. DiscardOldestPolicy 抛弃最老任务策略,把队列中最早的任务抛弃,新任务加入队列等待
    d. DiscardPolicy 抛弃策略,直接把新任务抛弃不执行

6、JVM 虚拟机

相关文章
|
2月前
|
Java 应用服务中间件 Linux
|
2月前
|
存储 关系型数据库 MySQL
|
2月前
|
Java 测试技术 Linux
生产环境发布管理
在一个大型团队中,生产发布是一件复杂的事情,从dev(前后端联调)-->test(测试集成&压力测试)-->pre(灰度测试)-->prod(生产环境)的多环境推进,以及生产环境的热更新、回滚等问题一直在困扰着各个公司,今天我将基于公司的自动化部署平台为大家讲解下我们是如何做到多环境部署。
|
2月前
|
运维 Devops 开发工具
生产环境缺陷管理
在一个大型团队中,bug协同管理是一件复杂的事情,发布经理要追版本bug,运维同学要评估bug影响范围,开发同学要在多个开发分支同时修复同一个bug,很容易出现bug漏提交、漏确认等生产安全问题。 本团队也出现过一起不同分支漏提交bugfix导致的一起P1故障(最高等级),该bug在生产环境进行hotfix时,漏掉了少量集群导致该二次故障。举个相似的例子,某品牌汽车发现潜在安全隐患进行召回,但却遗漏了某个小地区,偏偏在遗漏的地区,发生了安全事故导致有人员伤亡。 我们基于go-git开发实现了通用化的git-poison,通过分布式源码管理bug追溯、查询,可复制性高,适用于所有git仓库,与分
|
2月前
|
SQL 运维 分布式计算
|
2月前
|
Java 测试技术 Linux
生产环境发布管理
在一个大型团队中,生产发布是一件复杂的事情,从dev(前后端联调)-->test(测试集成&压力测试)-->pre(灰度测试)-->prod(生产环境)的多环境推进,以及生产环境的热更新、回滚等问题一直在困扰着各个公司,今天我将基于公司的自动化部署平台为大家讲解下我们是如何做到多环境部署。
|
2月前
|
SQL 运维 分布式计算
如何做好SQL质量监控
Cloud Native 在 SLS 中,用户可以通过 SQL 对日志数据(结构化、半结构化、无结构化)进行查询和分析。随着用户对 SQL 使用程度的不断加深,越来越多的用户希望了解自己使用 SQL 分析时的服务反馈(如请求量、成功率、数据量等等),以便对数据和分析行为进行精细管理或优化治理。 “现在我这个 Project 的 SQL 并发是多少?” “奇怪,我 SQL 请求并不多,为什么会有这么多 SQL 请求,是哪个业务线(Logstore)用的?” “我想了解我在 SLS 中使用 SQL 分析的整体情况,请问有什么监控数据或日志可以查看? 这些都是来自 SLS 真实用户的声音,可以看出用