当前位置: 首页>后端>正文

Java并发笔记

1、Java如何开启线程?怎么保证线程安全?

进程是操作系统进行资源分配的最小单位,一个进程包含多个线程,线程是操作系统进行任务分配的最小单位

1.继承thread类,重写run方法

2.实现runable接口,实现run方法

3.实现callable接口,实现call方法,通过FutureTask创建一个线程,获取线程执行的返回值

4.由线程池开启线程

保证线程安全:加锁,可以使用JVM提供的锁:synchronized,也可以使用JDK提供的各种锁:Lock

Java并发笔记,第1张
start启动之后
Java并发笔记,第2张

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的锁机制就是根据资源竞争的激烈程度不断进行锁升级的过程。

Java并发笔记,第3张

-XX:UseBiasedLocking:是否打开偏向锁,默认是不打开的

-XX:BiasedLockingStartupDelay:默认是4秒,等待多久打开偏向锁

4、ReentrantLock中tryLock()和lock()方法的区别

lock()阻塞加锁

tryLock()尝试加锁,不阻塞?

tryLock实现自旋锁方式

Java并发笔记,第4张

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执行后,线程进入阻塞状态,被调用的先执行

Java并发笔记,第5张

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()之后就变成这个状态了,线程池就没了

Java并发笔记,第6张

22、线程的状态

网上针对线程状态有很多,有5、6、7种,都可以

操作层面而言,有五种

new:新建状态

ready:就绪状态

running:运行状态

waiting:等待状态

terminated:结束状态

Java并发笔记,第7张
操作层面

Java层面有6种或者7种

new:新建状态

runnable:这里可以分为两个状态,运行状态和就绪状态

blocked:阻塞状态

waiting:等待状态

timed_wating:时间等待状态

terminated:结束状态

Java并发笔记,第8张
代码层面

23、结束线程的方式

1.stop(不用)

2.使用共享变量(很少使用)

3.interrupt()

24、线程池的执行流程?

Java并发笔记,第9张

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如何解决内存泄漏的问题?

Java并发笔记,第10张

30、Synchronized的底层实现原理

synchronized是通过对象的monitor(监视器锁)来完成的

1.如果线程访问对象的时候,拿到对象的monitor = 0,则这个线程就拥有了这个monitor的所有权,monitor = 1

2.如果线程已经占有这个对象的monitor,那当线程再一次进入的时候叫做重入,则monitor+1

3.如果其他线程想要使用这个对象,发现已经被占用了,则进入阻塞状态,直到monitor = 0时,再进行占用,重新获取monitor的所有权

31、JMM(Java内存模型)

Java并发笔记,第11张
Java并发笔记,第12张

read -> load?-> use -> assign?-> store?- > write?-> lock?-> unlock

Java并发笔记,第13张

32、MESI(缓存一致性协议)

Java并发笔记,第14张

修改缓存行的状态

M:修改Modified

E:互斥Exclusive

S:分享Shared

I:无效Invalid

33、Volatile缓存可见性实现原理

底层实现主要是通过汇编Lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定),并写回到主内存

1.会将当前处理器缓存行的数据立即写回到主内存中

2.这个写回主内存的操作,会引起其他CPU中缓存了该内存地址的数据,直接变成无效状态(MESI协议)

3.提供内存屏障功能,使Lock前后指令不能重排

内存屏障解决指令重排序,核心逻辑如下:

Java并发笔记,第15张
内存屏障

完全遵守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,再执行也无效

Java并发笔记,第16张
Locksupport案例

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++


https://www.xamrdz.com/backend/3za1934418.html

相关文章: