1、Java如何开启线程?怎么保证线程安全?
进程是操作系统进行资源分配的最小单位,一个进程包含多个线程,线程是操作系统进行任务分配的最小单位
1.继承thread类,重写run方法
2.实现runable接口,实现run方法
3.实现callable接口,实现call方法,通过FutureTask创建一个线程,获取线程执行的返回值
4.由线程池开启线程
保证线程安全:加锁,可以使用JVM提供的锁:synchronized,也可以使用JDK提供的各种锁:Lock
1.调用start方法,创建并关联内核线程
2.创建完毕后,底层回调我们的run方法(所有的创建线程方式都是包装并且替换我们的run方法)
创建线程又要到底层又要到内核,不如创建好线程就让他一直运行着,只需要包装一下run方法,等它执行完继续调用下一个run方法,就能把线程复用起来,这就是线程池的雏形
2、Volatile和Synchronized有什么区别?Volatile能不能保证线程安全?DCL(Double Check Lock)单例 为什么要加Volatile?
1.Synchronized关键字是用来加锁的,Volatile关键字是用来保证变量在线程中的可见性
2.不能,Volatile只能保证线程可见性,不能保证原子性
3.Volatile防止指令重排。在DCL中,防止高并发的情况下,指令重排造成的线程安全问题
3、Java线程锁机制是怎样的?偏向锁、轻量级锁、重量级锁有什么区别?锁机制是如何升级的?
jol:java.Object.layout
1. Java的锁就是在对象的Markword中记录一个锁状态。无锁、偏向锁、轻量级锁(自旋锁CAS while
)、重量级锁对应不同的锁状态。
2. Java的锁机制就是根据资源竞争的激烈程度不断进行锁升级的过程。
-XX:UseBiasedLocking:是否打开偏向锁,默认是不打开的
-XX:BiasedLockingStartupDelay:默认是4秒,等待多久打开偏向锁
4、ReentrantLock中tryLock()和lock()方法的区别
lock()阻塞加锁
tryLock()尝试加锁,不阻塞?
tryLock实现自旋锁方式
5、ReentrantLock中的公平锁和非公平锁的底层实现
不管是公平还是非公平都需要使用AQS去排队
公平锁:使用lock方法加锁时,会检查AQS队列中是否存在线程排队,如果存在老老实实去排队
非公平锁:使用lock方法加锁时,不检查,上去就竞争,竞争不到之后再老老实实去排队
不管是公平和非公平,一旦没有竞争到锁都会进行排队,当锁释放的时候,都是唤醒排在最前面的线程。
ReentrantLock是可重入锁
6、sleep()、wait()、yield()、join()的区别
锁池:所有需要竞争同步锁的线程都会放在锁池当中(竞争了一波,没竞争到的,就被放入锁池了)
等待池:调用wait()方法后,线程会放到等待池中
1. sleep是Thread类的静态本地方法,wait是Object类的本地方法
2. sleep不会释放lock,但wait会释放,而且会加入到等待队列
3. sleep不依赖于synchronized,wait依赖synchronized
4. sleep不需要被唤醒,wait需要被唤醒
5.?sleep一般用与线程休眠或暂停操作,wait一般用于多线程之间的通信
yield执行后,线程进入就绪状态,马上释放了cpu的执行权
join执行后,线程进入阻塞状态,被调用的先执行
7、Synchronized和ReentranLock的区别
1.sync是一个关键字,reen是一个类
2.sync可以自动加锁和释放锁,reen是程序员手动
3.sync的底层是JVM,reen底层是API
4.sync是非公平锁,reen可以公平,可以非公平
5.sync锁的是对象,reen锁的是线程,通过代码中int类型的state标识来标识锁的状态
6.sync有一个锁升级的过程,reen没有
8、ThreadLocal的底层原理
threadLocal是java提供的本地存储机制,底层是通过threadLocalMap实现的,key值为ThreadLocal对象,value值为缓存的值,使用threadLocal之后,一定要记得remove掉,否则会造成内存泄漏
9、ThreadLocal的使用场景
1.避免多次传递,打破层次间的约束?
2.线程间的数据隔离
3.进行事务操作,用于存储线程事务信息
4.数据库连接,session会话管理(spring框架底层的jdbc conn就是用这个实现的)
10、如何看线程死锁
jstack命令
11、线程之间如何进行通讯
一个机器上的两个线程:通过共享内存方式
两个机器上的两个线程:通过网络进行通信
共享内存方式:需要考虑并发问题,什么时候阻塞,什么时候唤醒,像java中的wait和notify
网络通信方式:通过网络连接将通信数据发给对方,当然也要考虑并发问题,解决方式就是加锁
12、并发、并行、串行的区别
串行:时间上不可能发生重叠,一个一个来,前一个没完成,后一个就必须要等着
并行:时间上可以重叠,同一时刻两个任务互不干扰的同时执行
并发:允许两个任务互相干扰,但是同一时间,只有一个任务再执行,交替执行(就是你执行一会,我执行一会)
13、并发的三大特性(保证线程安全)
1.原子性:Synchronized (CAS? ?要么全做,要么全不做)
2.可见性:Volatile、Synchronized、final
3.有序性(防止指令重排):Volatile、Synchronized
AtomicIntger保证了原子性和可见性
14、说说你对线程安全的理解
不是线程安全,应该说是内存安全,堆是共享内存,可以被所有线程访问
核心:当多个线程访问一个对象时,不需要加入其它手段,调用这个对象的行为都可以获取到正确的结果,那么我们就可以理解为这个线程是安全的
堆:所有进程和线程的共有空间,分为全局堆和局部堆,全局堆就是没有分配的空间,局部堆就是用户分配的空间,堆在操作系统初始化进程的时候分配,中间也可以向操作系统要,但是最后要还给操作系统,要不就内存泄漏
栈:是每个线程独有的,栈在每个线程开始的时候初始化,每个线程的栈都是独立的,所以是线程安全的
每个进程的内存空间中都会有一块公共区域,这就是堆(内存),进程内的所有线程都可以访问堆,这就是造成内存泄漏的潜在问题
15、Java死锁如何避免
1. 加锁的时候,注意顺序,
2. 给锁设置时限,到点就过期
3. 定期检查死锁
16、如果你提交任务时,线程池队列已满,这时会发生什么
如果使用的是无界队列,可以继续添加
如果使用的是有界队列,那就看看核心线程数有没有达到上限,如果没有则增加线程,如果达到上限了,则使用拒绝策略进行拒绝
17、Java中的四种引用类型(强、软、弱、虚)
1.强引用:User user = new User();? 它是不可能被垃圾回收机制回收,即便内存不足也不回收,是内存泄漏的主要原因之一
2.软引用:内存不足时,就会被回收
3.弱引用:即便内存充足,只要垃圾回收机制一运行,就会被回收
4.虚引用:不能单独使用,需要和队列联合使用,跟踪对象被垃圾回收的状态
18、AQS概述
AQS就是AbstractQueuedSynchronizer抽象类,AQS其实是JUC下面的一个基类,JUC下的很多内容都是基于AQS,比如ReentrantLock、ThreadPoolExecutor、阻塞队列、CountDownLatch
首先AQS中提供了一个由volatile修饰,并且采用CAS方式修改的int类型的state变量
其次AQS中维护了一个双向链表,有head,tail,并且每个节点都是node对象
19、创建线程池有哪些方式?
1.newFixedThreadPool:固定大小线程池,需要给他提供一个nThreads参数,即核心线程数和最大线程数,如果非要使用JDK提供的线程池,这是比较推荐的一款
2.newSignleThreadPool:单例线程池,如果涉及顺序消费,可以采用这个,核心线程数和最大线程数都是1
3.newCacheThreadPool:缓存线程池,核心线程数是0,不推荐使用
4.newScheduleThreadPool:定时任务线程池,需要提供核心线程数corePoolSize
5.newWorkStealingPool:工作窃取(分开治理)的线程池,每个线程都有自己的阻塞队列,如果没有再去别的阻塞队列拿
6.newSingleThreadScheduledExecutor:单例定时任务线程池
7.ThreadPoolExecutor:自定义线程池
不推荐使用JDK自带的线程池,推荐使用自定义线程池
20、线程池的核心参数
corePoolSize:核心线程数
maximumPoolSize:最大工作线程数
keepAliveTime:非核心线程数在阻塞队列的等待时间
unit:等待时间的单位
workQueue:任务再没有核心线程处理的时候,先扔到阻塞队列
threadFactory:线程工厂,构建线程的线程工作
handler:拒绝策略
21、线程池的状态
1.RUNNING:可执行的状态
2.SHUTDOWN:优雅的关闭,不再接受新任务,工作队列中的任务也会进行
3.STOP:不优雅的关闭,不再接受新任务,直接中断
4.TIDYING:一个过渡状态,线程池中没有任务了
5.TEIMINATED:执行terminated()之后就变成这个状态了,线程池就没了
22、线程的状态
网上针对线程状态有很多,有5、6、7种,都可以
操作层面而言,有五种
new:新建状态
ready:就绪状态
running:运行状态
waiting:等待状态
terminated:结束状态
Java层面有6种或者7种
new:新建状态
runnable:这里可以分为两个状态,运行状态和就绪状态
blocked:阻塞状态
waiting:等待状态
timed_wating:时间等待状态
terminated:结束状态
23、结束线程的方式
1.stop(不用)
2.使用共享变量(很少使用)
3.interrupt()
24、线程池的执行流程?
1.在线程池状态为running的状态,将任务扔到阻塞队列当中
2.加入阻塞队列成功的话,再次检查线程池状态,如果没问题的话再检查工作线程数
3.加入阻塞队列失败的话,将任务添加到非核心工作线程
25、为什么线程池要构建空任务的非核心线程数?
阻塞队列有任务,但是一个工作线程都没有,此刻没有人能够处理阻塞队列中的任务,就一直在阻塞队列中放着,直到有一个新任务过来,才会有可能去创建一个工作线程,后续才会去执行阻塞队列中存放的任务,然而这个任务处理的时机就太晚了
引发这个场景有两个:
1.核心线程数设置为0
2.设置参数allowCoreThreadTimeout =? true? 允许核心线程数超时??
26、线程池的核心参数到底如何设置?
核心线程数、阻塞队列、拒绝策略,最主要的三个
线程池的使用并不难,难点在于参数的配置,想调试出符合当前任务的线程池参数,最好的方式就是测试!
CPU密集型:corePoolSize = CPU核数 + 1
IO密集型:corePoolSize = CPU核数 * 2
阻塞队列:使用有界队列
27、ConcurrentHashMap在1.8做了什么优化?
1.存储结构:数组+链表+红黑树
2.存储操作:节点用CAS,链表用synchronized
3.扩容:不在基于segment,而是直接扩容
4.计数器:计数器选择不是AtomicLong,而是类似LongAdder的一个功能
28、ConcurrentHashMap的散列算法
不同于HashMap的是,不光低位加入运算,高位也加入了运算(将hashcode值往右推了16位),尽量打散数据,尽可能的分摊到hashmap的数组上
29、ThreadLocal如何解决内存泄漏的问题?
30、Synchronized的底层实现原理
synchronized是通过对象的monitor(监视器锁)来完成的
1.如果线程访问对象的时候,拿到对象的monitor = 0,则这个线程就拥有了这个monitor的所有权,monitor = 1
2.如果线程已经占有这个对象的monitor,那当线程再一次进入的时候叫做重入,则monitor+1
3.如果其他线程想要使用这个对象,发现已经被占用了,则进入阻塞状态,直到monitor = 0时,再进行占用,重新获取monitor的所有权
31、JMM(Java内存模型)
read -> load?-> use -> assign?-> store?- > write?-> lock?-> unlock
32、MESI(缓存一致性协议)
修改缓存行的状态
M:修改Modified
E:互斥Exclusive
S:分享Shared
I:无效Invalid
33、Volatile缓存可见性实现原理
底层实现主要是通过汇编Lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定),并写回到主内存
1.会将当前处理器缓存行的数据立即写回到主内存中
2.这个写回主内存的操作,会引起其他CPU中缓存了该内存地址的数据,直接变成无效状态(MESI协议)
3.提供内存屏障功能,使Lock前后指令不能重排
内存屏障解决指令重排序,核心逻辑如下:
完全遵守MESI会影响CPU的性能,引入了Store Buffer(存储队列)和Invalid Queue(失效队列)这两个队列又打破了一致性,所以CPU厂商把决定权交给程序员,你觉得哪里需要一致性,就在哪里加入内存屏障,这就叫按需一致性,分为读屏障和写屏障
写屏障:在写操作后面加入写屏障,CPU就必须等待存储队列里所有写操作都刷到对面的失效队列中
读屏障:在读操作前面加入读屏障,CPU就必须先把失效队列里的所有的消息都消费完,再去读变量值
这就是volatile的实现规范,如果一个Java程序员在CRUD的时候还要考虑在什么位置加入内存屏障,这也太难为我们了吧,所以JMM就是一套为Java程序员定义的规范,它有一个happens before原则,一共有六项,说的就是在这些情况下,程序员不需要去关心一致性的问题,他的底层实现就自带内存屏障!
34、四种拒绝策略
AbortPolicy?-?抛出异常,中止任务
CallerRunsPolicy?-?使用调用线程执行任务(执行当前的)
DiscardPolicy?-?直接丢弃
DiscardOldestPolicy?-?丢弃队列最老任务,添加新任务
35、锁膨胀、锁消除、锁粗化
锁膨胀:就是锁升级的过程? ? ?无锁-》偏向锁-》轻量级锁-》重量级锁
锁消除:在JIT(Just IN TIME)编译时,对运行上下文进行扫描,去除不可能存在竞争的锁
锁粗化:对于锁粒度的控制,锁的范围
36、ReadWriteLock是什么?
首先ReentrantLock某些时候有局限,如果使用ReentrantLock,是为了防止线程A写数据,线程B读数据,造成数据不一致,而线程C读数据,线程D也读数据,读数据不会改变数据,但还是加锁了,所以造成了资源的浪费,降低了性能
因为这个,诞生了ReadWriteLock,ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的具体实现,实现了读写分离,读锁是共享的,写锁是独占的,读和读不会互斥,读和写,写和读,写和写互斥,提升了写的性能
37、线程池execute()和submit()方法的区别是什么
execute()方法提交不需要返回值的任务,所以无法判断线程是否执行成功
submit()方法用于提交需要返回值的任务,线程池会返回一个future对象,future对象的get()方法来获取返回值
38、Executors、Executor和ThreadPoolExecutor的区别
Executors:工具类可以创建指定类型的线程池
Executor:执行我们的线程任务
ThreadPoolExecutor:自定义线程池
39、AQS是什么
AQS是一个锁框架,它定义了锁的实现机制,AQS底层是由同步队列+条件队列联手组成,AQS围绕这两个队列,提供了四大场景,分别是:获得锁、释放锁、条件队列的阻塞、条件队列的唤醒,分别对应着AQS架构图中的四种颜色的线走向
同步队列:管理着获取不到锁的线程的排队和释放
条件队列:在一定场景下,对同步队列的补充,比如获得锁的线程从空队列中拿数据,肯定是拿不到数据的,这个时候条件队列就会管理该线程,使该线程阻塞
40、CountDownLatch、CyclicBarrier、Semaphore
CountDownLatch:CountDownLatch中调用await方法线程需要等待所有调用countDown方法的线程执行,这就很适合一个业务需要一些准备条件,等准备条件准备好之后再继续执行,如果一些复杂的聚合查询,还有一些类似于广播消息的功能。
CyclicBarrier:在初始化时传入count值,在每次调用await方法时会count--,当count还大于0时使用当前lock的condition的await方法让当前线程进入到condition的等待队列中,当count等于0时执行构造器中传入的Runable的run方法,然后再调用condition的signalAll方法唤醒condition等待线程中所有等待的线程,再把count重置为初始值,然后所有线程都继续执行,看起来就像count数量的线程一批一批的执行,所以实际上cyclicBarrier就是借助了ReentrantLock的condition的await方法让线程进行等待count数量的线程就位,然后使用condition的signalAll方法通知所有线程一起执行,最后重置count如此往复。
Semaphore:semaphore一个典型的用户场景就是限流,像hystrix就是提供了两种限流方式:线程池和semaphore。semaphore允许规定数量的线程同时运行,但超过后的线程就需要等待前面的某个线程执行完后才能执行。这就很适合做限流
41、Synchronized、Lock、LockSupport区别
sync和lock,不能指定某一个线程的等待和释放,locksupport可以
Synchronized线程交互需要使用wait()和notify(),需要两个方法再同步代码块中,先执行wait(),在执行notify(),否则通知不到
Lock线程交互使用Condition对象的await()和signal()方法实现,需要两个方法再同步代码块中,先执行await(),在执行signal(),否则通知不到
LockSupport线程交互需要使用park()和unpark(),没有顺序,unpark()不会累加,最多为1,再执行也无效
42、Java对象的布局
1.Java对象头? -- 固定? ?压缩:8 + 4? 不压缩:8 + 8? ?
2.Java对象的实例数据? --? 不固定
3.对齐填充? 8的倍数对齐
jol-core 依赖,查看Java对象数据
ClassLayout.parseInstance(Obejct).toPrintable()
43、Java对象头由什么组成
Mark Word
Class Metadata Address
JVM ------------------------------------------------- 规范/标准--------------------------女朋友/白富美
HotSpot ---------------------sun公司-------------产品/实现---------------------------天海翼/波多野结衣
openjdk----------------项目------代码/c++