Java多线程 面试知识点总结1
一.JMM(Java内存模型)
参考
- 老刘-JMM面试包过
- HollisChuang-Java内存模型
- 《Java并发编程实战》Chapter-16
- 《深入理解Java虚拟机》Chapter-12.3.6
背景:由于CPU和主内存间存在数量级的速率差,CPU-多级缓存-主存这种硬件架构来作为缓冲,CPU将常用的数据放在高级缓存中,运算结束后CPU再将结果同步到主存中。同样的,也会从主存中获取数据,然后在各个缓存中进行计算,但是这样会引入缓存一致性的问题。此外为了提升CPU的执行效率,还会进行处理器优化,包括指令重排序。由此,缓存一致性会产生可见性问题,CPU时间片可能会造成原子性问题,指令重排序会造成有序性问题。为了解决这个问题,就想到在物理机器上定义一套内存模型,规范内存的读写操作,解决多线程通过主存通信时存在的原子性,可见性以及有序性问题,保证程序在各种平台下对内存的访问都能得到一致的访问效果
Java中,内存模型是一种规范,定义了很多内容包括:
1.所有的变量都存储在主存中,每个线程都有自己的工作内存,保存了来自主存的变量的拷贝,每个线程只能操作自己工作内存中的变量,而无法直接操作主存以及访问其他线程的变量。线程之前的通信需要通过主存完成。
2.线程与主存之间的通信有8个操作,可以浓缩为4个
主 → read load 工作 → store write 主
3.JMM是一种规范,说明了某个线程的内存操作在哪些情况下对于其他线程时可见的,其中包括这些操作是按照 happens-before的偏序关系进行排序,从而保证了Java内存模型中各个操作的有序性。
**happens-before **(先行发生原则):JMM中定义两个操作偏序关系
- 程序顺序规则:在一个线程内,按照控制流的顺序,书写在前面的操作先行发生在后面的操作;
- 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作;
- volatile变量规则:对一个volatile的写操作要先发生于后面对同一变量的读操作;
- 线程启动规则:Thread对象的 start() 先发生于此线程的每一个动作;
- 线程终止规则:线程中所有的操作都先发生在对这个线程的终止检测;
- 线程中断规则:对线程interrupt()方法的调用先发生于被中断线程的代码检测到中断事件的发生;
- 对象终结规则:一个对象的初始化完成先行发生于它的finalize()方法的开始;
- 传递性:操作A先行发生于B,B先发生于C,则A先行发生于C。
4.内存模型也封装了底层的实现后提供给开发者一些关键字和并发包,比如 volatile synchronized,以及JUC包。
二.JUC并发包:
1.并发工具类
闭锁 CountDownLatch:处理一个线程等待多个线程执行完成之后才能执行的场景,等待的是事件,主要操作有 countDown/await,有一个计时器(实质是AQS中的state变量),不可重用;(Future/ FutureTask也能实现闭锁效果,见 极客Java并发编程《Future》煮茶叶的例子)
循环栅栏 Cyclic Barrier:一组线程之间互相等待,有计数器,屏障点,可选的冲出屏障后的任务,用到了可重入锁,会自动重置可重复使用;
信号量 :许可证 acqure release 实现资源池 和有界阻塞队列
2.Lock显式锁
可重入锁,可重入读写锁等
3.原子类
涉及到CAS的相关内容
4.并发容器
ConcurrentHashMap(1.7和1.8底层源码) CopyandWriteArrayList BlockingQueue(Array- Linked- Priority-)
5.Executor 框架
1.基于 Executors的静态方法之一构建的线程池:
- newFixedThreadPool:固定长度
- newCachedThreadPool:可缓存,回收空闲的线程池
- newSingleThreadExecutor:单线程
- newScheduledThreadPool:固定长度,延迟/定时
- 。。。
涉及到线程池 ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){}
里面涉及到 阻塞队列
4种拒绝饱和策略:
- AbortPolicy:默认的,拒绝并抛出异常;
- Discard:抛弃任务不报异常;
- DiscardOldest:会抛弃下一个要执行的任务,然后尝试重新提交新的任务;
- CallerRuns:在一个调用了Execute的线程种执行该任务
设置策略:
- CPU密集的任务:设置线程少,阻塞队列长,能降低CPU使用率,但是限制吞吐量,有个经验公式 核数+1
- IO密集任务:线程数 = CPU核数*(1+平均等待时间/平均工作时间)
三.Synchronized底层实现原理
参考
- 做个好人君-死磕synchronized底层实现
- 《深入理解Java虚拟机》Chapter13 13.3
key:Java对象头和Monitor是实现syn的基础。
3.1 Synchronized
synchronized 是一个关键字,用于修饰方法或代码块,能够将其锁起来。这个锁是互斥锁,一次只允许一个线程进入被锁的代码块中,从而在并发环境下实现同步功能。
传统意义上的 syn重量级锁是内置锁(monitor锁,可以是任何一个Java对象) 依赖 系统的同步函数,在Linux上使用的是mutex互斥锁,这些同步函数涉及到用户态和内核态的切换,进程的上下文切换,成本较高。在没有多线程竞争,或者两个线程接近交替执行的情况下,使用 重量级锁 效率低下。
就是为了解决上面两个场景的效率低下的问题,JDK1.6后引入了两种新锁机制:偏向锁,轻量级锁。而二者的实现与JVM中对象的对象头有关。对象头中有一个MarkWord,存储了对象锁状态信息(锁标志位,偏向锁信息)。
偏向锁:当JVM开启偏向锁模式,只有一个线程获取锁时,锁对象Markword通过CAS操作存储该线程的ID,等该线程再次想获取锁的时候,就无需 CAS操作直接获取;
轻量级锁:当有另一个线程竞争偏向锁,交替执行同步代码块时,偏向锁结束,升级为轻量级锁,JVM会在当前线程的栈帧中创建一个lock record,通过CAS操作将Lock Record的地址存储在对象头的markword中,操作成功则说明 该线程成功获取这个锁,如果失败说明至少有一个线程在竞争,它会先检查Markword是否指向当前线程的栈帧,是就是重入,直接进同步块。不是就说明已经有另一个线程抢占了锁对象。如果出现两个以上竞争同一把锁,就膨胀为重量级锁。
此时的Markword存储的是指向重量级锁的指针,后面等待锁的线程也必须进入阻塞状态。
3.2 synchronized优化(JVM 1.6)
自旋锁 && 自适应自旋锁:避免线程挂起和恢复的开销,让请求锁的线程等一会(自旋),但不放弃cpu的执行时间,看看锁会不会马上被释放。虽然避免了线程切换的开销,但是要占用CPU时间。自适应自旋锁是指 自旋次数不固定,而是根据前一次在同一个锁的自旋时间及锁的拥有着的状态来决定。
锁消除 JVM即时编译器运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除,如果判断数据不会被其他线程访问到,就认为是线程私有的,就无需加锁
锁粗化: 在编写代码的时候,会尽量将同步块的作用范围限制得尽量小-只有在涉及到共享数据的才进行同步。但是如果一系列连续操作都对同一个对象反复加锁解锁会导致不必要的性能损耗,因此如果JVM检测到这种情况就会将同步的范围扩展到整个操作序列的外部,这样只需要加一次锁。
四.ReentrantLock (aka: Rel)
- 手动显式添加锁-释放锁(要在finally中释放);
- 提供可轮询,可定时,可中断的锁获取方式,其中可轮询和可定时能够避免死锁的发生;
- 提供公平锁的实现;
- 非块结构加锁。
与Synchronized相比,ReL是类,前者是关键字,且能提供以上的额外功能。同时实现机制不同:
- sync操作的是 MarkWord,lock调用是 Unsafe类的 park()方法
五. Volatile VS Synchromized的区别
volatile:修饰的变量一旦被某个线程修改,其他线程立即得到通知,并从主存中获取最新的值,可以实现可见性和有序性
通过lock关键字 和 CPU锁定内存来实现可见性;
当写一个volatile变量时,JMM会把该线程的工作内存的共享变量值更新到主内存中;
当读一个volatile变量时,JMM会把该线程的工作内存设置为无效
通过内存屏障实现有序性
通过插入内存屏障指令禁止在内存屏障前后的指令执行重排序优化
区别
- volatie本质告诉jvm当前变量在工作内存中值不确定,需要从主存中读取,sync则是锁住当前变量,只有当前线程能读取,在 重量级锁实现中,其他线程只能阻塞等待;
- v 修饰变量,sync 修饰级别包括 变量 方法 类
- volatile只能实现 变量 修改的可见性,以及有序性,不能保证原子性,因此不能保证线程安全;而Volatile都可能保证,因此线程安全;
- volatile不会造成线程阻塞,后者可能导致线程阻塞;
- volatile修饰 变量不会被编译器优化,而sync标记的变量可以被编译器优化
六.死锁
定义
类型
锁顺序死锁以及动态锁顺序死锁):两个线程获得锁的顺序交替进行,顺序不固定导致;
解决办法:固定加锁的顺序,比如利用 System.identityHashCode的方法来确定获取锁的顺序
协作对象之间发生的死锁:两个线程隐性的从不同顺序获取两个锁
解决:开放调用,即调用某个方法时不需要持有锁,需要使同步代码块仅用于保护涉及共享状态的操作
Java多线程 面试知识点总结1的更多相关文章
- Java多线程面试问答
今天,我们将讨论Java 多线程面试问答. 线程是Java面试问题中的热门话题之一.在这里,我从面试的角度列出了大多数重要的Java多线程面试问题,但是您应该对Java线程有足够的知识来处理后续问题. ...
- Java多线程面试15道
Java 线程面试问题 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分.如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题.在投资银行业务中多线程和并发是 ...
- JAVA多线程面试必看(转载)
JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...
- java工程师-面试知识点总结
目录(转载) [x] 一.Java基础(语言.集合框架.OOP.设计模式等) [x] 二.Java高级(JavaEE.框架.服务器.工具等) [x] 三.多线程和并发 [x] 四.Java虚拟机 [x ...
- 【转】Java多线程面试问题集锦
如果你即将去一家从事大型系统研发的公司进行Java面试,不可避免的会有多线程相关的问题.下面是一些针对初学者或者新手的问题,如果你已经具备良好的基础,那么你可以跳过本文,直接尝试针对进阶水平的Java ...
- java多线程面试中常见知识点
1.进程和线程 (1)进程是资源分配的最小单位,线程是程序执行的最小单位. (2)进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段.堆栈段和数据段,这种操作非 ...
- java后台面试知识点总结
本文主要记录在准备面试过程中遇到的一些基本知识点(持续更新) 一.Java基础知识 1.抽象类和接口的区别 接口和抽象类中都可以定义变量,但是接口中定义的必须是公共的.静态的.Final的,抽象类中的 ...
- Java多线程面试问题
这篇文章主要是对多线程的面试问题进行总结的,罗列了40个多线程的问题. 1. 多线程有什么用? 一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡.所谓 ...
- java多线程面试总结
一:基本知识点 1.1线程与进程区别: 1.进程是资源分配的最小单位,线程是CPU调度的最小单位 2.一个进程由一个或多个线程组成 3.进程之间相互独立,每个进程都有独立的代码和数据空间,但同一进程下 ...
随机推荐
- Web 前端开发规范手册
一.规范目的 Web 前端开发规范手册 1.1 概述 ......................................................................... ...
- java swagger ui 添加header请求头参数
我用到的swagger 主要有三款产品,swagger editor,swagger ui 和swagger codegen. swagger editor:主要是一个本地客户端,用来自己添加api, ...
- Python—IP地址与整数之间的转换
1. 将整数转换成IP: 思路:将整数转换成无符号32位的二进制,再8位进行分割,每8位转换成十进制即可. 方法一:#!usr/bin/python 2 #encoding=utf-8 3 #1. 将 ...
- Flink进入大厂面试准备,收藏这一篇就够了
1. Flink 的容错机制(checkpoint) Checkpoint机制是Flink可靠性的基石,可以保证Flink集群在某个算子因为某些原因(如 异常退出)出现故障时,能够将整个应用流图的状态 ...
- Kotlin Coroutine(协程): 二、初识协程
@ 目录 前言 一.初识协程 1.runBlocking: 阻塞协程 2.launch: 创建协程 3.Job 4.coroutineScope 5.协程取消 6.协程超时 7.async 并行任务 ...
- 【Spring】Spring中的循环依赖及解决
什么是循环依赖? 就是A对象依赖了B对象,B对象依赖了A对象. 比如: // A依赖了B class A{ public B b; } // B依赖了A class B{ public A a; } ...
- 给potplayer配置iptv源,看所有你想看的电视
目录 一.展示: 二.下载 三.播放 一.展示: 二.下载 Github 上的开源项目:iptv-org/iptv 传送门: https://github.com/iptv-org/iptv 该项目包 ...
- Swift-使用transform 实现重复平移动画
摘要 要实现一组重复的动画,本质上就是找到动画开始点.结束点.在动画结束的时候,触发开始点,持续这样的动作. 这里面要梳理的逻辑就是1.触发开始点和2.监听动画结束点.这两个逻辑是实现重复动画的基础. ...
- 家庭账本开发day11
编写登录界面和个人信息查看界面 $.ajax({ url: "UserServlet?method=login",//url ...
- MapReduce处理数据1
学了一段时间的hadoop了,一直没有什么正经练手的机会,今天老师给了一个课堂测试来进行练手,正好试一下. 项目已上传至github:https://github.com/yandashan/MapR ...