线程与并发

简介:

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 虚拟机

相关文章
|
22小时前
|
存储 消息中间件 开发框架
应用架构图
在上一节有了业务架构的基础之上,当我们需要落地具体的技术方案时,此时就需要技术人员开始考虑技术架构了。技术架构是应接应用架构的技术需求,并根据识别的技术需求,进行技术选项,把各个关键技术和技术之间的关系描述清楚。 基础结构解决的主要问题包括:如何进行技术层面的分层、开发框架的选择、开发语言的选择、涉及非功能性需求的技术选择。由于应用架构体系是分层的,那么对应的技术架构体系自然也是分层的。大的分层有微服务架构分层模型,小的则是单个应用的技术分层框架。大的技术体系考虑清楚后,剩下问题就是根据实际业务考虑选择具体的技术点。各个技术点的分析、方案选择,最终形成关键技术清单,关键技术清单应考虑架构本身的
|
22小时前
|
Java
|
22小时前
|
Java 应用服务中间件 Linux
|
22小时前
|
存储 算法 Java
|
22小时前
|
存储 关系型数据库 MySQL
|
22小时前
|
Java 测试技术 Linux
生产环境发布管理
在一个大型团队中,生产发布是一件复杂的事情,从dev(前后端联调)-->test(测试集成&压力测试)-->pre(灰度测试)-->prod(生产环境)的多环境推进,以及生产环境的热更新、回滚等问题一直在困扰着各个公司,今天我将基于公司的自动化部署平台为大家讲解下我们是如何做到多环境部署。
|
22小时前
|
运维 Devops 开发工具
生产环境缺陷管理
在一个大型团队中,bug协同管理是一件复杂的事情,发布经理要追版本bug,运维同学要评估bug影响范围,开发同学要在多个开发分支同时修复同一个bug,很容易出现bug漏提交、漏确认等生产安全问题。 本团队也出现过一起不同分支漏提交bugfix导致的一起P1故障(最高等级),该bug在生产环境进行hotfix时,漏掉了少量集群导致该二次故障。举个相似的例子,某品牌汽车发现潜在安全隐患进行召回,但却遗漏了某个小地区,偏偏在遗漏的地区,发生了安全事故导致有人员伤亡。 我们基于go-git开发实现了通用化的git-poison,通过分布式源码管理bug追溯、查询,可复制性高,适用于所有git仓库,与分
|
1天前
|
SQL 运维 分布式计算