收藏 分销(赏)

Java多线程-知识点梳理和总结-超详细-面试知识点.docx

上传人:二*** 文档编号:4518330 上传时间:2024-09-26 格式:DOCX 页数:25 大小:38.99KB
下载 相关 举报
Java多线程-知识点梳理和总结-超详细-面试知识点.docx_第1页
第1页 / 共25页
亲,该文档总共25页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

1、Java多线程知识点梳理和总结面试知识点一、volatile1.可见性(1)当被volatile修饰的变量进行写操作时,这个变量将会被直接 写入共享内存,而非线程的专属存储空间。根本原因是在写入后执行了一个空操作,使得cpu的cache写入内 存(2)当读取一个被volatile修饰的变量时,会直接从共享内存中 读,而非线程专属的存储空间中读。2 .有序性(1) volatile在指令间加上了内存屏障,内存屏障指的是重排序的 时候不能把后面的指令重排序到内存屏障之前的位置。3 .原子性(不能保证)(1)原子性指的是一组操作必须一起完成,中途不能被中断。(2) volatile能确保long、d

2、ouble读写的原子性java内存模型保证声明为volatile的long和double变量的get和 set操作是原子的。4 .应用场景(1)多个变量之间或者某个变量的当前值与修改后值之间没有约 束。状态标志全局变量一读多写c. 一个 segment 包含了一个 hashEntry 链表2) hashEntrya.链表结构的元素b. hashEntry的成员变量除了 value都定义为finala)为了维护链表结构,防止并发问题 JDK1.81) Node数组2)链表3)红黑树(2)常用方法底层实现(JDK1.7) put()1)流程a. 1.首先Hash定位到Segmentb. 2.对当前

3、Segment加锁,如果Segment中元素的数量超过了阈 值,那么需要进行扩容并且进行rehashc. 3.再定位到链表头部 get()1)不用加锁,是非阻塞的a.因为共享变量都定义为了 volatilea)根据java内存模型的happen before原那么,对volatile字段的写 入操作先于读操作,即使两个线程同时修改和获取volatile变量,get 操作也能拿到最新的值,这是用volatile替换锁的经典应用场景b) volatile能够保证内存可见性2)需要进行两次Hash操作,第一次Hash定位到Segment,第二次 Hash定位到元素所在的链表的头部3)定位到链表头部之

4、后根据key取出对应的value值a.如果取出的value是null,那么对取出value这一过程进行加锁 (lock()a)取出的value是null的原因是可能现在正在进行put操作b.如果不是value,那么直接返回value值(3) remove)1)因为HashEntry中的next是final的,一经赋值以后就不可修 改,所以在定位到待删除元素e的位置以后,程序就将待删除元素前 面的那一些元素全部复制一遍,然后再一个一个重新接到链表上去。 尾结点指向e的下一个结点。e后面的结点不需要复制,它们可以重 用。e之前的元素在remove。之后为remove之前的逆置 size()1) s

5、ize()操作涉及到多个segment2) size操作就是遍历了两次Segment,每次记录Segment的 modCount值,然后将两次的modCount进行比拟,如果相同,那么表示 期间没有发生过写入操作,就将原先遍历的结果返回,如果不相同, 那么把这个过程再重复做一次,如果再不相同,那么就需要将所有的 Segment都锁住,然后一个一个遍历了(3)特点key和value都不可以为nullget()方法不加锁,是非阻塞的是线程平安的JDK 1.8使用了 CAS操作来支持更高的并发度,在CAS操作失败 时使用内置锁synchronizedo并且JDK 1.8的实现也在链表过长时会转 换为

6、红黑树。(4)常用方法底层实现(JDK1.7)2. ConcurrentSkipListMap 和 ConcurrentSkipListSet3. ConcurrentLinkedQueue4. CopyOnWriteArrayList 和 CopyOnWriteArraySet六、线程分类1 .用户线程一般是程序中创立的线程2 .守护线程(1)为用户服务的线程,当所有用户线程停止时才会被终止,如 JVM的垃圾回收(2)通过Thread.setDaemon(true)方法设置守护线程七、Java内存模型1 .主内存2 .工作内存(1)线程开始运行时会将所需的变量从主内存中拷贝一份到工作 内存中

7、,在线程运行结束后再写入主内存3 .主内存和工作内存的交互read,writejock,unlock,assgin,use,load,save4 .三大特征(1)可见性一条线程修改完一个共享变量后,另一个线程假设访问这个变量将 会访问到修改后的值(2)有序性 Happens-Before 规那么1)如果一个操作执行的结果需要对另一个操作可见,那么这两个 操作必须要存在一个happen-before的关系重排序1)为了减少内存操作速度远慢于CPU运行速度所带来的CPU空置 的影响,虚拟机会按照自己的一些规那么对代码的执行顺序进行调整, 从而提高执行效率。程序代码执行的结果不受JVM指令重排序的影

8、响(3)原子性一组操作必须一起完成,中途不能被中断。八、synchronized1 . 一个重量级的可重入锁(1)监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock 来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核 心态,这个状态之间的转换需要相比照拟长的时间,时间本钱相对较 高,这也是为什么早期的synchronized效率低的原因2 .作用方式(1)作用于代码块(2)作用于实例方法持有的是当前对象实例的锁(3)作用于静态方法持有的是静态对象的锁3 .底层实现(1)作用于方法(显式同步)JVM可以从方法区中的方法表结构中的ACC_SYNCHRONIZED访问

9、标志区分一个方法是否同步方法。(2)作用于代码块(隐式同步)编译时,在代码块的前后加上monitorenter和monitorexit1)为了保证在方法异常完成时monitorenter和monitorexit指令依 然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常 处理器声明可处理所有的异常,它的目的就是用来执行monitorexit指 令ObjectMontior waitSet EntryList(3) owner count4 .线程中断与synchronized(1)线程的中断操作对于正在等待获取的锁对象的synchronized方 法或者代码块并不起作用5 .等待唤醒机

10、制与synchronized(1)在使用notify/notifyAII和wait这3个方法时,必须处于 synchronized 代码块或者synchronized 方法中(2) notify/notifyAII 和 wait 方法都依赖于 monitor6. java对象头Mark Word对象的hashCodeCG年代锁信息(偏向锁,轻量级锁,重量级锁)GC标志指向monitor的指针Class Metadata Address指向对象实例的指针九、线程1 .线程中断public static boolean interrupted()测试当前线程是否已经中断,并将线程状态设置为fals

11、epublic boolean islnterruptedf)测试线程是否已经中断。线程的中断状态不受该方法的影响public void interrupt)中断线程,设置中断标识为为trueInterruptedException对线程调用interrupt。时,如果该线程处于阻塞或者等待状态, 那么就会抛出 InterruptedException2 .线程状态(1)新建继承Thread类创立线程实现Runnable接口创立线程使用Callable和Future创立线程使用线程池例如用Executor框架(2)运行(3)阻塞同步阻塞1)竞争锁失败等待阻塞1)调用 wait,lockSupp

12、ort.park。等方法其他阻塞(4)就绪调用 thread.start()(5)结束线程运行完毕3.线程间通信(1) wait/notify/notifyAII(2) synchronized/lock(3) thread 方法 thread.join() thread.yiled()(4)管道通信管道流pipeStream1)是一种特殊的流,用于在不同线程间直接传送数据。一个线程 发送数据到输出管道,另一个线程从输入管道中读数据。(5)同步工具类 CountDownLatch1) CountDownLatch允许一个或多个线程等待其他线程完成操作a. countDownf)a) count

13、 数减 1b. awaitf)a)线程阻塞直到count等于02)应用场景a.开启多个线程分块下载一个大文件,每个线程只下载固定的一 截,最后由另外一个线程来拼接所有的分段。b.应用程序的主线程希望在负责启动框架服务的线程已经启动所 有的框架服务之后再执行。c.确保一个计算不会执行,直到所需要的资源被初始化。 CyclicBarrier1) CyclicBarrier (int ),参数表示屏障拦截的线程数量,每个线程 调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被 阻塞。2)当CyclicBarrier的count数等于。的时候,阻塞的线程都继续执 行3)应

14、用场景a.多线程计算(3) Semphore1)可以控制同时访问的线程个数,它维护了一组“许可证”。a. acquire()a)消费一个许可证。如果没有许可证了,会阻塞起来b. release)a)添加一个许可证2)应用场景a.可以用于做流量控制,特别是公用资源有限的应用场景,比方 数据库连接 CountDownLatch 和 CyclicBarrier 的区另U1) CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数 器可以使用reset。方法重置2) CountDownLatch.await 一般阻塞主线程,而 CyclicBarrierton 一般 阻塞

15、工作线程3) CountDownLatch主要用于描述一个或者多个线程等待其他线程 执行完毕4) CyclicBarrier主要用于描述多个线程之间相互等待 Exchanger1) Exchanger用于进行线程间的数据交换。它提供一个同步点,在 这个同步点,两个线程可以交换彼此的数据。这两个线程通过 exchange方法交换数据,如果第一个线程先执行exchange。方法,它会 一直等待第二个线程也执行exchange方法,当两个线程都到达同步点 时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对 方。4.线程平安(1)所谓的线程平安问题,本质在于线程对共享变量的操作的原 子性,可

16、见性,有序性不能同时满足,因此解决线程平安问题的关键 在于使其同时满足以上三个特征十、线程池(Executor)1 .线程池基本概念(1)线程池可以看做是线程的集合,请求到来时线程池给这个请 求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不 是销毁)。这样就实现了线程的重用。2 . ThreadPoolExecutor(1)构造函数阻塞队列策略1)同步移交a. SynchronousQueuea)不存储元素的阻塞队列,因此超出核心线程数的任务会创立新 的线程来指执行。2)无界队列a.常用的为无界的LinkedBlockingQueueb.当任务耗时较长时可能会导致大量新任务在队列

17、中堆积最终导 致00Mc. DelayedWorkQueuea) 一个按超时时间升序排序的队列b)使用了优先级队列的无界阻塞队列,支持延时获取,所谓延时 队列就是消费线程将会延时一段时间来消费元素。队列里的元素要实 现 Delay 接口。3)有界队列a.常见队列a) ArrayBlockingQueuei.遵循FIFO的队列二、JUC1. LockReentrantlock特点1)可实现公平锁a.在构造函数中设置a)通过fair这个boolean型变量设置b.是通过hasQueuedPredecessors。函数实现的a)这个函数会判断当前获取锁的线程是否是请求队列的首部线程2)绑定多个条件(

18、Condition)3)等待可中断4)可重入锁a.通过 setExclusiveOwnerThread。实现内部类1) Reentrantlock 默认实现在 sync 中(sync 继承了 AQS)2)非公平锁 NonfairSync (继承 sync)3)公平锁 FairSync(继承 sync)ReentrantReadWriteLock特点1) state的变量高16位是读锁,低16位是写锁。2)读锁不能升级为写锁3)写锁可以降级为读锁4)当访问方式是读取操作时,使用读锁即可,当访问方式是修改操作时,那么使用写锁b) LinkedBlockingQueue1 .有节的 LinkedBl

19、ockingQueuec) PriorityBlockingQueue1 .优先级队列b.可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量(因为任务数量少了)拒绝策略1)丢弃当前任务2)丢弃最老的任务3)直接抛出异常4)抛回调用者的线程处理(2)线程池状态 running1)自然是运行状态,指可以接受任务执行队列里的任务 shutdonw1) SHUTDOWN指调用了 shutdown。方法,不再接受新任务了,但 是阻塞队列里的任务得执行完毕。 stop1) STOP指调用了 shutdownNow()方法,不再接受新任务,同时抛 弃阻塞队列里的所有任务并中断所有正在执行

20、任务。 tidying1)所有任务都执行完毕,在调用shutdown()/shutdownNow()中都 会尝试更新为这个状态。 terminated1)当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已 经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED 状态(3)线程池的配置10密集型1)尽量使用较小的线程池,一般为CPU核心数+1Cpu密集型1)可以使用稍大的线程池,一般为2*CPU核心数。混合型3.常见的线程池newFixedThreadPool固定大小的线程池,有新任务时,根据当前线程池的线程数量确 定后续步骤创立参数1) LinkedBlock

21、ingQueue(无界)2)线程存活时间:永久存活3)核心线程数:n(用户指定)4)最大线程数:n(用户指定)newCachedThreadPool当有新任务时,直接新建线程创立参数1) SynchronousQueue2)线程存活时间:60s3)核心线程数:04)最大线程数:Interget.MAX_VALUEnewSingleThreadExecutor创立单个线程的线程池。创立参数1) LinkedBlockingQueue(无界)2)线程存活时间:永久存活3)核心线程数:14)最大线程数:1NewScheduledThreadPool:创立一个定长线程池,支持定时及周期性任务执行。创立

22、参数1) DelayedWorkQueuea. 一个按超时时间升序排序的队列b.使用了优先级队列的无界阻塞队列,支持延时获取,所谓延时 队列就是消费线程将会延时一段时间来消费元素。队列里的元素要实 现 Delay 接口。2)线程存活时间:永久存活3)核心线程数:用户指定4)最大线程数:lnteger.MAX_VALUE4 .线程池的关闭Shutdown线程池状态变为shutdownShutdownNow线程状态变为stop(3)中断线程池中的某一个线程的方法可以通过使用submitf)方法来提交一个线程,它会返回一个 Future对象,通过调用该对象的cancel(true)方法就可以中断线程

23、。5 .常见接口和类Executor 接口只有一个void excute(Runnable task)方法,用户执行任务ExecutorService Executor的子接口,定义了 sumbit,invokeAII/nvokeAny 等方法AbstractExecutorService实现了 sumbit,invokeAII。等方法6 .源码WorkerWorker是线程池中的线程继承AQS,实现runnable1)既是一个可执行的任务,又可以到达锁的效果初始state=-l这样构造的原因主要是为了实现对中断的控制1) 1.worker未运行时a. 1.shutdown。线程池时,会对每个

24、worker tryLock。上锁,tryAcquire是尝试通过CAS将state由0设置为1,因此会失败b. 2.shutdownNow()线程池时,不用tryLock。上锁,但调用worker.interruptlfStarted()lh worker 时也要求 state=0,因此也会失败2) 2.worker 运行时3) runWorker中,会对正在运行中的worker加锁,所以如果调用 了 shutdown。方法,中断也会失败,但是如果调用shutdownNow。方 法,该方法会通过worker.interruptlfStarted来中断任务addWorker1.判断线程池状态,

25、如果状态正常,进入下一步2.比拟当前线程池的线程数和最大线程数/核心线程数的大小(由 参数core确定比拟对象),如果没有超过的话,进入下一步,否那么返回 false3.在线程池自带的成员变量ReentrantLock的加锁的情况下,向 Workers的HashSet中添加新创立的worker实例,添加完成后解锁, 并start该worker实例,worker.start()方法底层其实调用的就是 runWorker。方法基本概念:创立新线程并执行runWorker1.将state设置为02.在mainLock.lock的情况下,进行task.run 3.在 finally 块中释放锁 mai

26、nLock.unlock4.在使用getTask方法去阻塞队列中获取锁十一、线程死锁1 .死锁产生的四个必要条件(1)互斥条件:资源是独占的且排他使用,进程互斥使用资源, 即任意时刻一个资源只能给一个进程使用,其他进程假设申请一个资 源,而该资源被另一进程占有时,那么申请者等待直到资源被占有者释 放。(2)不可剥夺条件:进程所获得的资源在未使用完毕之前,不被 其他进程强行剥夺,而只能由获得该资源的进程资源释放。(3)请求和保持条件:进程每次申请它所需要的一局部资源,在 申请新的资源的同时,继续占用已分配到的资源。(4)循环等待条件:在发生死锁时必然存在一个进程等待队列 Pl,P2,.,Pn淇中

27、P1等待P2占有的资源,P2等待P3占有的资 源,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一 个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一 个进程所申请的资源。2 .解决方法(1)解决任一条件内部类1) ReentrantReadWriteLock 默认实现在 sync 中(sync 继承了 AQS)2) NonfairSync3) FairSync4) WriteLock (实现 Lock 接口)5) ReadLock (实现 Lock 接口)synchronized 和 lock 的区别synchronized是关键字,lock是接口发生异常时,synchro

28、nized会自动释放锁,lock需要手动释放锁lock能够知道是否成功获取到锁,synchronized那么不行1)通过tryAcquire。去尝试获取锁,获取成功那么设置锁状态并返回true,否那么返回false0在竞争激烈的情况下,lock效率更高1) JDK1.5之前Lock效率高很多2) JDK1.6之后效率差不多lock可以实现等待可中断,synchronized不可以1)通过tryLock(long time, TimeUnit unit)设置等待时间,如果规定时 间内没获取到锁,才返回falselock可以实现公平锁lock可以实现读写锁2. Atomic(1)对数据进行操作时(

29、使用CAS来保证)可以保证原子性(2)存在CAS的ABA问题使用AtomicStampedReference的版本号机制来管理(3)通过unsafe实现的包装类Unsafe底层实际上是调用C代码,C代码调用汇编,最后生成出 一条CPU指令cmpxchg,完成操作 CompareAndSwap.()(4) JDK增加了 LongAdder等四个类,高并发时效率更高,但是消 耗空间更多3. AQS(1)一个抽象的队列式的同步器,定义了一套多线程访问共享资 源的同步器框架(实现锁的框架),许多同步类实现都依赖于它,本 质上其实就是对于state的获取和释放(2) ReentrantLock/Reen

30、trantReadWriteLock/Semaphore/ CountDownLatch的实现都依赖于它(3)数据结构共享资源 volatile int state默认实现了 FIFO线程等待队列,底层是双向链表(4)线程模式独享模式1) tryAcquire(int)/tryAcquireShared(int)共享模式1) tryRelease/tryReleaseShared(int)既可独占,也可共享(ReentrantReadWriteLock)4.线程池相关callable一般和ExecutorService配合来使用有返回值future判断任务是否完成1) future.isDon

31、ef)返回执行结果1) future.get()中断线程的执行1) future.cancel)futureTaskfuture接口的唯一实现类既可以作为Runnable被线程执行,又可以作为Future得到 Callable的返回值。5. CAS算法(1) CAS需要有3个操作数:内存地址V,旧的预期值A,即将要 更新的目标值B。,CAS指令执行时,当且仅当内存地址V的值与预期 值A相等时,将内存地址V的值修改为B,否那么就什么都不做。整个 比拟并替换的操作是一个原子操作。使用AtomicStampedReference的版本号机制来管理ABA问题如果内存地址V初次读取的值是A,并且在准备赋

32、值的时候检查 到它的值仍然为A,但可能期间它已经被修改过了解决方法:使用AtomicStampedReference的版本号机制来管理(3)通常将CAS用于同步的方式是从地址V读取值A,执行多步计 算来获得新值B,然后使用CAS将V的值从A改为B。如果V处的值尚 未同时更改,那么CAS操作成功。(3) CAS操作是一条CPU指令,不会被打断,所以是原子操作三、锁分类1 .乐观锁,悲观锁(1)乐观锁CAS算法(2)悲观锁 synchronized2 .独享锁,共享锁3 .公平锁,非公平锁4 .可重入锁(1)线程得到一个对象锁后再次请求该对象锁,是允许的(2)实现方法为每一个锁关联一个获取计数器和

33、一个所有者线程,在获取锁的 时候判断owner,如果是相同的owner,那么count加15 .分段锁(1)通过分段锁的形式来细化锁的粒度,从而实现高效的并发操作,例如 concurrentHashMap6 .锁优化(1)偏向锁CAS操作:将线程ID保存在对象的Mark Word中1)如果成功,那么说明该线程已经获取了对象的偏向锁有其他线程获取对象锁时失效1)对象锁定时膨胀为轻量级锁2)对象未锁定时恢复到未锁定状态经验依据:在大多数情况下,锁不仅不存在多线程竞争,而且总 是由同一线程屡次获得(2)轻量级锁CAS操作:将对象的Mark Word更新为指向Lock Record的指针1)如果成功,

34、那么该线程已经获取了对象的轻量级锁2)如果失败,检查对象的Mark Word是否指向当前线程a.如果指向当前线程,那么进入同步代码块b.如果没有,那么膨胀为重量级锁如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为 重量级锁。经验依据:对绝大局部的锁,在整个同步周期内都不存在竞争(3)重量级锁(4)自旋锁尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获 取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPUo(5)锁粗化如果一串零碎的操作都对同一个对象加锁,将会把加锁同步的范 围扩展(粗化)到整个操作序列的外部(6)锁消除对于一些代码上要求同步,但实际上并不需要同步

35、的锁进行消除四、ThreadLocal1 . ThreadLocal提供了线程的局部变量,且不会和其他线程的局部 变量进行冲突,实现了线程的数据隔离。2 .基本用法set(value) 首先获取当前thread的ThreadLocalMap1)如果map已经初始化,那么将kv存入map中2)否那么初始化map(此时构造函数已经将kv存入)get()(1) removedinitialValue()3 .底层实现ThreadLocalMap ThreadLocal 的内部类(1) set():向 ThreadLocalMap 设置值,key 是 ThreadLocal 对象, 值是传递进来的对象

36、get。:从 ThreadLocalMap 获取值,key 是 ThreadLocal 对象4 . ThreadLocalMap(1)用Entry进行存储,Entry的key的类型为ThreadLocal(2)每个Thread维护了一个ThreadLocalMap的成员变量,这是实 现ThreadLocal的核心hash冲突解决方法:线性探测法5 .内存泄漏(1) ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key 会被回收,而Value不会回收,这时候ThreadLocalMap里面就会存在 key为null但是value不为null的entry项。如果创立ThreadLo

37、cal的线 程一直持续运行,那么这个Entry对象中的value就有可能一直得不到 回收,发生内存泄露。(2)解决方法调用threadLocal.remove。方法来清理key为null的元素(3)根本原因由于ThreadLocalMap的生命周期跟Thread 一样长,如果没有手 动删除对应key的value就会导致内存泄漏,而不是因为弱引用(4)为什么key要用弱引用引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持 有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回 收,导致Entry内存泄漏。6 .应用场景(1)单个线程(2)线程上下文信息存储(3)数据库连接(3) session 管理五、并发容器1. concurrentHashMap(1)数据结构 JDK1.71) segmenta.继承了 ReentrantLockb. 一个 concurrentHashMap 包含了一个 segment 数组

展开阅读全文
相似文档                                   自信AI助手自信AI助手
猜你喜欢                                   自信AI导航自信AI导航
搜索标签

当前位置:首页 > 考试专区 > 其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        获赠5币

©2010-2024 宁波自信网络信息技术有限公司  版权所有

客服电话:4008-655-100  投诉/维权电话:4009-655-100

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :gzh.png    weibo.png    LOFTER.png 

客服