面试必备——Java多线程与并发(二)
1.synchroized相关(锁的是对象,不是代码)
(1)线程安全问题的主要原因
- 存在共享数据(也称临界资源)
- 存在多线程共同操作这些共享数据
(2)互斥锁的特性
(3)获取的锁的分类
1)获取对象锁
两种用法
- 同步代码块(synchronized(this),synchronized(类实例对象))),锁是小括号中的实例对象
- 同步非静态方法(synchronized method),锁是当前对象的实例对象
2)获取类锁
两种用法
- 同步代码块(synchronized(类.class)),锁是小括号中的类对象
- 同步静态方法(synchronized static method),锁是当前对象的类对象
3)总结
- 有线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块
- 若锁住的是同个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步代码块的线程会被阻塞
- 若锁住的是同个对象,一个线程在访问对象的同步方法时,另一个访问对象同步方法的线程会被阻塞
- 若锁住的是同个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步方法的线程会被阻塞,反之亦然
- 同一个类的不同对象的对象锁互不干扰
- 类锁由于也是一种特殊的对象锁,因此表现和上述1,2,3,4一致,而由于一个类只有一把对象锁,所以用一个类的不同对象使用类锁将会是同步的
- 类锁和对象锁互不干扰
(4)底层实现原理
1)对象在内存中的布局
- 对象头
- 实例数据
- 对齐填充
2)实现基础
- Java对象头
- Monitor:每个Java对象天生自带了一把看不见的锁,Monitor锁(内部锁),也称为管程或监视器锁,可以理解为一种同步工具(机制),通常它被描述为一个对象
3)什么是重入
4)为什么会对synchronize的嗤之以鼻
- 早期版本中,synchronized属于重量级锁,依赖于Mutex Lock实现
- 线程之间的切换需要从用户状态转换到核心态,开销较大
5)自旋锁和自适应自旋锁
①自旋锁(java4已经引入,默认关闭,Java6默认开启)
- 许多情况下,共享数据的锁定状态持续时间较短,切换线程不值得
- 通过让线程执行忙循环(类似While(true))等待锁的释放,不让出CPU
- 缺点:若锁被其他线程长时间占用,会带来许多性能上的开销
②自适应自旋锁
- 自旋的次数不再固定
- 由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
③锁消除


1 public void add(String str1, String str2){
2 //StringBuffer是线程安全的,sb只会在append方法中使用,其他线程不可能引用
3 //不属于共享资源,JVM会自动消除内部的锁
4 StringBuffer sb = new StringBuffer();
5 sb.append(str1).append(str2);
6 }
④锁粗化


1 public static void run(){
2 int i =0;
3 StringBuffer buffer = new StringBuffer();
4 while(i<100){
5 buffer.append(i);
6 }
7 }
6)synchronized四种状态
- 无锁、偏向锁、轻量级锁、重量级锁
- 锁膨胀方向:无锁->偏向锁->轻量级锁->重量级锁
①偏向锁:减少同一线程获取锁的代价 CAS(Compare And Swap)
- 大多数情况下,锁不存在多线程竞争,总是由同一线程多次获得
- 不适用于锁竞争比较激烈的多线程场合
②轻量级锁
- 轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁
- 适应的场景:线程交替执行同步块
- 若存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁
③synchronize加锁的解锁的过程
加锁
- 在代码进入同步块时,如果同步对象锁状态为无锁状态,虚拟机会在当前线程的栈帧中创建一个锁记录(Lock Record)的空间,用于存储当前对象的Mark Word的拷贝。
- 拷贝对象头中的Mark Word到锁记录中。
- 拷贝成功后,使用CAS操作尝试将对象的Mark Word更新为指向锁记录的指针,并将锁记录里的owner指向Mark Word。
- 如果更新成功,那么当前线程则获取了该对象的锁,并将Mark Word里的锁标志位设置为锁状态。
- 如果更新失败,则判断Mark Word是否指向当前栈帧,如果是,则说明当前线程之前已经获取该锁,然后进入同步块继续执行,如果不是,则说明别的线程正在请求该锁,进行锁膨胀,根据锁状态,判断是进行自旋等待或者进入锁池中。
解锁
- 利用CAS操作尝试把当前线程中复制的Mark Word对象替换当前对象中的Mark Word。
- 如果替换成功,则同步完成
- 如果替换失败,则说明在该线程执行过程中,有其他线程尝试过获取该锁(此时锁已膨胀),那就要释放锁的同时,唤醒被挂起的线程。
④锁的内存语义
- 当线程释放锁时,Java内存模型会把该线程对应的本地内存中的共享变量刷新到主内存中;
- 当线程获取锁时,Java内存模型会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量
⑤汇总
2.ReentrantLock
(1)基本
- 位于java.util.concurrent.locks包
- 和CountDownLatch、FutureTask、Samaphore一样基于AQS实现
- 能够实现比synchronized更细粒度的控制,如控制fairness
- 调用lock()之后,必须调用unlock()释放锁
- 性能未必比synchronized高,并且也是可重入的
(2)公平性的设置
- ReentrantLock fariLock = new ReentrantLock(true);
- 参数为true时,倾向于将锁赋予等待时间最久的线程;
- 公平锁:获取锁的顺序按先后调用lock方法的顺序(慎用)
- 非公平锁:抢占的顺序不一定,看运气
- synchronized是非公平锁
(3)ReentrantLock将锁对象化
- 判断是否有线程,或者某个特定线程,在排队等待获取锁
- 带超时的获取锁的尝试
- 感知有没有成功获取锁
3.Synchronized和ReentrantLock的区别
- synchronized是关键字,ReentrantLock是类
- ReentrantLock可以对获取锁的等待时间进行设置,避免死锁
- ReentrantLock可以获取各种锁的信息
- ReentrantLock可以灵活地实现多路通知
- 机制:sync操作Mark Word,lock调用Unsafe类的park()方法
4.Java内存模型
5.JMM中的主内存和工作内存
(1)JMM中的主内存
- 存储Java实例对象
- 包含成员变量、类信息、常量、静态变量等
- 属于数据共享的区域,多线程并发操作时会引发线程安全问题
(2)JMM中的工作内存
- 存储当前方法的所有本地变量信息,本地变量对其他线程不可见
- 字节码行号指示器、Native方法信息
- 属于线程私有数据区域,不存在线程安全问题
(3)JMM与Java内存区域划分是不同的概念层次
- JMM描述的是一组规则(通过规则控制各个变量在共享区域和私有区域的访问方式),围绕原子性、有序性、可见性展开
- 相似点:存在共享区域和私有区域(在JVM中,主内存属于共享区域,包含堆和方法区;而工作内存私有区域,包含程序计数器,虚拟机栈和本地方法栈)
(4)主内存与工作内存的数据存储类型以及操作方式归纳
- 方法里的基本数据类型本地变量将直接存储在工作内存的栈帧结构中
- 引用类型的本地变量:引用存储在工作内存中,实例存储在主内存中
- 成员变量、static变量、类信息均会被存储在主内存中
- 主内存共享的方式是线程各拷贝一份数据到工作内存,操作完成后刷新回主内存
6.JMM如何解决可见性问题
(1)问题描述
(2)指令重排序需要满足的条件
- 在单线程环境下不能改变程序运行的结果
- 存在数据依赖关系的不允许重排序
(3)happens-before的八大原则
(4)happens-before的概念
(5)volatile:JVM提供的轻量级同步机制
- 保证被volatile修饰的共享变量对所有线程总是可见的
- 禁止指令重排序优化
(6)volatile可见性
(7)内存屏障
- 保证特定操作的执行顺序
- 保证某些变量的内存可见性
(8)volatile如何禁止重排序化
- 通过插入内存屏障指令禁止在内存屏障前后的指令执行重排序优化
- 强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本
(9)volatile和synchronized的区别
- volatile本质是在告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主内存中读取;synchronized则是锁定当前变量,只有当前线程可以访问变量,其他线程被阻塞住直到该线程完成变量操作为止
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法和类级别
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量修改的可见性和原子性
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
7.CAS(Compare and Swap)
(1)概念
- 支持原子更新操作,适用于计数器,序列发生器等场景
- 属于乐观锁机制,号称lock-free
- CAS操作失败时由开发者决定是否继续尝试,还是执行别的操作
(2)思想
(3)CAS多数情况下对开发者来说是透明的
- J.U.C的atomic包提供了常用的原子性数据类型以及引用、数组等相等原子类型和更新操作工具,是很多线程安全程序的首选
- Unsafe类虽提供CAS服用,但因能够操纵任意内存地址读写而有隐患
- Java9以后,可以使用Variable Handle API来替代Unsafe
(4)缺点
- 若循坏时间长,开销很大
- 只能保证一个共享变量的原子操作
- ABA问题(解决:AutomicStampedReference,通过控制版本来解决)
8.Java线程池
(1)利用Executors创建不同的线程池满足不同场景的需求
(2)Fork/Join框架
1)分而治之
2)工作窃取算法
(3)为什么要使用线程池
- 降低资源消耗
- 提高线程的可管理性
(4)Executor框架
(5)J.U.C的三个Executor接口
- Executor:运行新任务的简单接口,将任务提交和任务执行细节解耦
- ExecutorService:具备管理执行器和任务生命周期的方法,提交任务机制更完善
- ScheduledExecutorService:支持Future和定期执行任务
(6)ThreadPoolExecutor
(7)ThreadPoolExecutor的构造函数
- corePoolSize:核心线程数据
- maximumPoolSize:线程不够用时能够创建的最大线程数
- workQueue:任务等待队列
- keepAliveTime:抢占的顺序不一定,看运气
- threadFactory:创建新线程,Executors.defaultThreadFactory()
(8)handler:线程池的饱和策略
- AbortPolicy:直接抛出异常,这是默认策略
- CallerRunsPolicy:用调用者所在的线程来执行任务
- DiscardOldestPolicy:丢弃队列中靠最前的任务,并执行当前任务
- Doscar的Policy:直接丢弃任务
- 实现RejectedExecutionHandler接口的自定义handler
(9)新任务提交execute执行后的判断
- 如果运行的线程少于corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;
- 如果线程池中的线程数量大于等于corePoolSize且小于maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务;
- 如果设置的corePoolSize和maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取任务并处理;
- 如果运行的线程数据大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务
(10)线程池的状态
- RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务
- SHUTDOWN:不能接受新提交的任务,但可以处理存量任务
- STOP:不再接受新提交的任务,也不能处理存量任务
- TIDYTING:所有任务都已停止
- TERMINATED:terminated()方法执行完后进入该状态
(11)状态转换图
(12)工作线程的生命周期
(13)线程池的大小如何选定
- CPU密集型(针对计算的场景):线程数=按照核数或者核数+1设定
- I/O密集型(处理较多等待任务):线程数=CPU核数*(1+平均等待时间/平均工作时间)
面试必备——Java多线程与并发(二)的更多相关文章
- 面试必备——Java多线程与并发(一)
1.进程和线程的 (1)由来 1)串行 最初的计算机只能接受一些特定的指令,用户输入一个指令,计算机就做出一个操作.当用户在思考或者输入时,计算机就在等待.显然这样效率低下,在很多时候,计算机都处在等 ...
- 互联网校招面试必备——Java多线程
本文首发于我的个人博客:尾尾部落 本文是我刷了几十篇一线互联网校招java后端开发岗位的面经后总结的多线程相关题目,虽然有点小长,但是面试前看一看,相信能帮你轻松啃下多线程这块大骨头. 什么是进程,什 ...
- JAVA多线程和并发基础面试问答(转载)
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- [转] JAVA多线程和并发基础面试问答
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- JAVA多线程和并发基础面试问答
转载: JAVA多线程和并发基础面试问答 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对 ...
- 【多线程】JAVA多线程和并发基础面试问答(转载)
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- (转)JAVA多线程和并发基础面试问答
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- JAVA多线程和并发基础面试问答【转】
JAVA多线程和并发基础面试问答 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰 ...
- 17、JAVA多线程和并发基础面试问答
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
随机推荐
- ElasticSearch 搜索引擎概念简介
公号:码农充电站pro 主页:https://codeshellme.github.io 1,倒排索引 倒排索引是一种数据结构,经常用在搜索引擎的实现中,用于快速找到某个单词所在的文档. 倒排索引会记 ...
- redis持久化-AOF
1.aof文件写入与同步 2.aof重写 重写的目的是为了减小aof文件的体积,redis服务器可以创建一个新的aof文件来代替现有的aof文件,新文件不会有冗余的命令. BGREWRITEAOF:遍 ...
- Netty(五)Netty 高性能之道
4.背景介绍 4.1.1 Netty 惊人的性能数据 通过使用 Netty(NIO 框架)相比于传统基于 Java 序列化+BIO(同步阻塞 IO)的通信框架,性能提升了 8 倍多.事 实上,我对这个 ...
- CVE-2018-8120 提权
经验证,诸多版本的Windows系统均存在该漏洞,文末同时附带一份利用该漏洞制作的提权工具,以供学习.经测试该工具支持Win2003 x32/x64.WinXP x32.Win7 x32/x64, W ...
- Vue & Sentry sourcemaps All In One
Vue & Sentry sourcemaps All In One vue & sentry & sourcemaps https://docs.sentry.io/plat ...
- 微信分享 API
微信分享 API https://market.cmbchina.com/MPage/online/190416201200302/wechatShare.js /* * 注意: * 1. 所有的JS ...
- how to enable vue cli auto open the localhost url
how to enable vue cli auto open the localhost URL bad you must click the link by manually, waste of ...
- how HTTPS works
How HTTPS works HTTPS comic tutorials How HTTPS works ...in a comic! https://howhttps.works/ A cat e ...
- js 实现各种数据结构 APP
js 实现各种数据结构 APP 常见数据结构: 数组,队列,栈,堆,链表,集合,字典,散列表,树, 图 Array, Queue, Link, Collection, Set,Map, HashMap ...
- js & void() & void(0)
js & void() & void(0) https://www.runoob.com/js/js-void.html void() <a href="javascr ...