资源描述
Java多线程知识点梳理和总结面试知识点
一、volatile
1.可见性
(1)当被volatile修饰的变量进行写操作时,这个变量将会被直接 写入共享内存,而非线程的专属存储空间。
①根本原因是在写入后执行了一个空操作,使得cpu的cache写入内 存
(2)当读取一个被volatile修饰的变量时,会直接从共享内存中 读,而非线程专属的存储空间中读。
2 .有序性
(1) volatile在指令间加上了内存屏障,内存屏障指的是重排序的 时候不能把后面的指令重排序到内存屏障之前的位置。
3 .原子性(不能保证)(1)原子性指的是一组操作必须一起完成,中途不能被中断。
(2) volatile能确保long、double读写的原子性
①java内存模型保证声明为volatile的long和double变量的get和 set操作是原子的。
4 .应用场景
(1)多个变量之间或者某个变量的当前值与修改后值之间没有约 束。
①状态标志
②全局变量
③一读多写
c. 一个 segment 包含了一个 hashEntry 链表
2) hashEntry
a.链表结构的元素
b. hashEntry的成员变量除了 value都定义为final
a)为了维护链表结构,防止并发问题
② JDK1.8
1) Node数组
2)链表
3)红黑树(2)常用方法底层实现(JDK1.7)
① put()
1)流程
a. 1.首先Hash定位到Segment
b. 2.对当前Segment加锁,如果Segment中元素的数量超过了阈 值,那么需要进行扩容并且进行rehash
c. 3.再定位到链表头部
② get()
1)不用加锁,是非阻塞的
a.因为共享变量都定义为了 volatile
a)根据java内存模型的happen before原那么,对volatile字段的写 入操作先于读操作,即使两个线程同时修改和获取volatile变量,get 操作也能拿到最新的值,这是用volatile替换锁的经典应用场景
b) volatile能够保证内存可见性
2)需要进行两次Hash操作,第一次Hash定位到Segment,第二次 Hash定位到元素所在的链表的头部
3)定位到链表头部之后根据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) size()操作涉及到多个segment
2) size操作就是遍历了两次Segment,每次记录Segment的 modCount值,然后将两次的modCount进行比拟,如果相同,那么表示 期间没有发生过写入操作,就将原先遍历的结果返回,如果不相同, 那么把这个过程再重复做一次,如果再不相同,那么就需要将所有的 Segment都锁住,然后一个一个遍历了(3)特点
①key和value都不可以为null
②get()方法不加锁,是非阻塞的
③是线程平安的
④JDK 1.8使用了 CAS操作来支持更高的并发度,在CAS操作失败 时使用内置锁synchronizedo并且JDK 1.8的实现也在链表过长时会转 换为红黑树。
(4)常用方法底层实现(JDK1.7)
2. ConcurrentSkipListMap 和 ConcurrentSkipListSet
3. ConcurrentLinkedQueue
4. CopyOnWriteArrayList 和 CopyOnWriteArraySet
六、线程分类
1 .用户线程一般是程序中创立的线程
2 .守护线程
(1)为用户服务的线程,当所有用户线程停止时才会被终止,如 JVM的垃圾回收(2)通过Thread.setDaemon(true)方法设置守护线程
七、Java内存模型
1 .主内存
2 .工作内存
(1)线程开始运行时会将所需的变量从主内存中拷贝一份到工作 内存中,在线程运行结束后再写入主内存
3 .主内存和工作内存的交互read,writejock,unlock,assgin,use,load,save
4 .三大特征(1)可见性
①一条线程修改完一个共享变量后,另一个线程假设访问这个变量将 会访问到修改后的值(2)有序性
① Happens-Before 规那么
1)如果一个操作执行的结果需要对另一个操作可见,那么这两个 操作必须要存在一个happen-before的关系
②重排序
1)为了减少内存操作速度远慢于CPU运行速度所带来的CPU空置 的影响,虚拟机会按照自己的一些规那么对代码的执行顺序进行调整, 从而提高执行效率。
③程序代码执行的结果不受JVM指令重排序的影响(3)原子性
①一组操作必须一起完成,中途不能被中断。
八、synchronized
1 . 一个重量级的可重入锁
(1)监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock 来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核 心态,这个状态之间的转换需要相比照拟长的时间,时间本钱相对较 高,这也是为什么早期的synchronized效率低的原因
2 .作用方式(1)作用于代码块
(2)作用于实例方法
①持有的是当前对象实例的锁(3)作用于静态方法
①持有的是静态对象的锁
3 .底层实现(1)作用于方法(显式同步)
①JVM可以从方法区中的方法表结构中的ACC_SYNCHRONIZED访问 标志区分一个方法是否同步方法。
(2)作用于代码块(隐式同步)
①编译时,在代码块的前后加上monitorenter和monitorexit
1)为了保证在方法异常完成时monitorenter和monitorexit指令依 然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常 处理器声明可处理所有的异常,它的目的就是用来执行monitorexit指 令ObjectMontior
① waitSet
② EntryList
(3) owner
④ count
4 .线程中断与synchronized
(1)线程的中断操作对于正在等待获取的锁对象的synchronized方 法或者代码块并不起作用
5 .等待唤醒机制与synchronized
(1)在使用notify/notifyAII和wait这3个方法时,必须处于 synchronized 代码块或者synchronized 方法中(2) notify/notifyAII 和 wait 方法都依赖于 monitor
6. java对象头Mark Word
①对象的hashCode
②CG年代
③锁信息(偏向锁,轻量级锁,重量级锁)
④GC标志
⑤指向monitor的指针Class Metadata Address
①指向对象实例的指针
九、线程
1 .线程中断public static boolean interrupted()
①测试当前线程是否已经中断,并将线程状态设置为falsepublic boolean islnterruptedf)
①测试线程是否已经中断。线程的中断状态不受该方法的影响public void interrupt))
①中断线程,设置中断标识为为trueInterruptedException
①对线程调用interrupt。时,如果该线程处于阻塞或者等待状态, 那么就会抛出 InterruptedException
2 .线程状态(1)新建
①继承Thread类创立线程
②实现Runnable接口创立线程
③使用Callable和Future创立线程
④使用线程池例如用Executor框架
(2)运行
(3)阻塞
①同步阻塞
1)竞争锁失败
②等待阻塞
1)调用 wait,lockSupport.park。等方法
③其他阻塞
(4)就绪
①调用 thread.start()
(5)结束
①线程运行完毕
3.线程间通信
(1) wait/notify/notifyAII
(2) synchronized/lock
(3) thread 方法
① thread.join()
② thread.yiled()
(4)管道通信
①管道流pipeStream
1)是一种特殊的流,用于在不同线程间直接传送数据。一个线程 发送数据到输出管道,另一个线程从输入管道中读数据。
(5)同步工具类
① CountDownLatch
1) CountDownLatch允许一个或多个线程等待其他线程完成操作
a. countDownf)
a) count 数减 1
b. awaitf)
a)线程阻塞直到count等于0
2)应用场景
a.开启多个线程分块下载一个大文件,每个线程只下载固定的一 截,最后由另外一个线程来拼接所有的分段。
b.应用程序的主线程希望在负责启动框架服务的线程已经启动所 有的框架服务之后再执行。
c.确保一个计算不会执行,直到所需要的资源被初始化。
② CyclicBarrier
1) CyclicBarrier (int ),参数表示屏障拦截的线程数量,每个线程 调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被 阻塞。
2)当CyclicBarrier的count数等于。的时候,阻塞的线程都继续执 行
3)应用场景
a.多线程计算
(3) Semphore
1)可以控制同时访问的线程个数,它维护了一组“许可证”。
a. acquire()
a)消费一个许可证。如果没有许可证了,会阻塞起来
b. release))
a)添加一个许可证
2)应用场景
a.可以用于做流量控制,特别是公用资源有限的应用场景,比方 数据库连接
④ CountDownLatch 和 CyclicBarrier 的区另U
1) CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数 器可以使用reset。方法重置
2) CountDownLatch.await 一般阻塞主线程,而 CyclicBarrierton 一般 阻塞工作线程
3) CountDownLatch主要用于描述一个或者多个线程等待其他线程 执行完毕
4) CyclicBarrier主要用于描述多个线程之间相互等待
⑤ Exchanger
1) Exchanger用于进行线程间的数据交换。它提供一个同步点,在 这个同步点,两个线程可以交换彼此的数据。这两个线程通过 exchange方法交换数据,如果第一个线程先执行exchange。方法,它会 一直等待第二个线程也执行exchange方法,当两个线程都到达同步点 时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对 方。
4.线程平安
(1)所谓的线程平安问题,本质在于线程对共享变量的操作的原 子性,可见性,有序性不能同时满足,因此解决线程平安问题的关键 在于使其同时满足以上三个特征
十、线程池(Executor)
1 .线程池基本概念
(1)线程池可以看做是线程的集合,请求到来时线程池给这个请 求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不 是销毁)。这样就实现了线程的重用。
2 . ThreadPoolExecutor(1)构造函数
①阻塞队列策略
1)同步移交
a. SynchronousQueue
a)不存储元素的阻塞队列,因此超出核心线程数的任务会创立新 的线程来指执行。
2)无界队列
a.常用的为无界的LinkedBlockingQueue
b.当任务耗时较长时可能会导致大量新任务在队列中堆积最终导 致00M
c. DelayedWorkQueue
a) 一个按超时时间升序排序的队列
b)使用了优先级队列的无界阻塞队列,支持延时获取,所谓延时 队列就是消费线程将会延时一段时间来消费元素。队列里的元素要实 现 Delay 接口。
3)有界队列
a.常见队列
a) ArrayBlockingQueue
i.遵循FIFO的队列
二、JUC
1. LockReentrantlock
①特点
1)可实现公平锁
a.在构造函数中设置
a)通过fair这个boolean型变量设置
b.是通过hasQueuedPredecessors。函数实现的
a)这个函数会判断当前获取锁的线程是否是请求队列的首部线程
2)绑定多个条件(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) LinkedBlockingQueue
1 .有节的 LinkedBlockingQueue
c) PriorityBlockingQueue
1 .优先级队列
b.可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量(因为任务数量少了)
②拒绝策略
1)丢弃当前任务
2)丢弃最老的任务
3)直接抛出异常
4)抛回调用者的线程处理(2)线程池状态
① running
1)自然是运行状态,指可以接受任务执行队列里的任务
② shutdonw
1) SHUTDOWN指调用了 shutdown。方法,不再接受新任务了,但 是阻塞队列里的任务得执行完毕。
③ stop
1) STOP指调用了 shutdownNow()方法,不再接受新任务,同时抛 弃阻塞队列里的所有任务并中断所有正在执行任务。
④ tidying
1)所有任务都执行完毕,在调用shutdown()/shutdownNow()中都 会尝试更新为这个状态。
⑤ terminated
1)当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已 经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED 状态
(3)线程池的配置
①10密集型
1)尽量使用较小的线程池,一般为CPU核心数+1
②Cpu密集型
1)可以使用稍大的线程池,一般为2*CPU核心数。
③混合型
3.常见的线程池newFixedThreadPool
①固定大小的线程池,有新任务时,根据当前线程池的线程数量确 定后续步骤
②创立参数
1) LinkedBlockingQueue(无界)
2)线程存活时间:永久存活
3)核心线程数:n(用户指定)
4)最大线程数:n(用户指定)newCachedThreadPool
①当有新任务时,直接新建线程
②创立参数
1) SynchronousQueue
2)线程存活时间:60s
3)核心线程数:0
4)最大线程数:Interget.MAX_VALUEnewSingleThreadExecutor
①创立单个线程的线程池。
②创立参数
1) LinkedBlockingQueue(无界)
2)线程存活时间:永久存活
3)核心线程数:1
4)最大线程数:1NewScheduledThreadPool:
①创立一个定长线程池,支持定时及周期性任务执行。
②创立参数
1) DelayedWorkQueue
a. 一个按超时时间升序排序的队列
b.使用了优先级队列的无界阻塞队列,支持延时获取,所谓延时 队列就是消费线程将会延时一段时间来消费元素。队列里的元素要实 现 Delay 接口。
2)线程存活时间:永久存活
3)核心线程数:用户指定
4)最大线程数:lnteger.MAX_VALUE
4 .线程池的关闭Shutdown
①线程池状态变为shutdownShutdownNow
①线程状态变为stop(3)中断线程池中的某一个线程的方法
①可以通过使用submitf)方法来提交一个线程,它会返回一个 Future<?>对象,通过调用该对象的cancel(true)方法就可以中断线程。
5 .常见接口和类Executor 接口
①只有一个void excute(Runnable task)方法,用户执行任务ExecutorService
① Executor■的子接口,定义了 sumbit,invokeAII/nvokeAny 等方法AbstractExecutorService
①实现了 sumbit,invokeAII。等方法
6 .源码Worker
①Worker是线程池中的线程
②继承AQS,实现runnable
1)既是一个可执行的任务,又可以到达锁的效果
③初始state=-l
④这样构造的原因主要是为了实现对中断的控制
1) 1.worker未运行时
a. 1.shutdown。线程池时,会对每个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来中断任务addWorker
①1.判断线程池状态,如果状态正常,进入下一步
②2.比拟当前线程池的线程数和最大线程数/核心线程数的大小(由 参数core确定比拟对象),如果没有超过的话,进入下一步,否那么返回 false
③3.在线程池自带的成员变量ReentrantLock的加锁的情况下,向 Workers的HashSet中添加新创立的worker实例,添加完成后解锁, 并start该worker实例,worker.start()方法底层其实调用的就是 runWorker。方法
④基本概念:创立新线程并执行runWorker
①1.将state设置为0
②2.在mainLock.lock的情况下,进行task.run
③ 3.在 finally 块中释放锁 mainLock.unlock
④4.在使用getTask方法去阻塞队列中获取锁
十一、线程死锁
1 .死锁产生的四个必要条件
(1)互斥条件:资源是独占的且排他使用,进程互斥使用资源, 即任意时刻一个资源只能给一个进程使用,其他进程假设申请一个资 源,而该资源被另一进程占有时,那么申请者等待直到资源被占有者释 放。
(2)不可剥夺条件:进程所获得的资源在未使用完毕之前,不被 其他进程强行剥夺,而只能由获得该资源的进程资源释放。
(3)请求和保持条件:进程每次申请它所需要的一局部资源,在 申请新的资源的同时,继续占用已分配到的资源。
(4)循环等待条件:在发生死锁时必然存在一个进程等待队列 {Pl,P2,...,Pn}淇中P1等待P2占有的资源,P2等待P3占有的资 源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一 个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一 个进程所申请的资源。
2 .解决方法(1)解决任一条件
②内部类
1) ReentrantReadWriteLock 默认实现在 sync 中(sync 继承了 AQS)
2) NonfairSync
3) FairSync
4) WriteLock (实现 Lock 接口)
5) ReadLock (实现 Lock 接口)synchronized 和 lock 的区别
①synchronized是关键字,lock是接口
②发生异常时,synchronized会自动释放锁,lock需要手动释放锁
③lock能够知道是否成功获取到锁,synchronized那么不行
1)通过tryAcquire。去尝试获取锁,获取成功那么设置锁状态并返回true,否那么返回false0
④在竞争激烈的情况下,lock效率更高
1) JDK1.5之前Lock效率高很多
2) JDK1.6之后效率差不多
⑤lock可以实现等待可中断,synchronized不可以
1)通过tryLock(long time, TimeUnit unit)设置等待时间,如果规定时 间内没获取到锁,才返回false
⑥lock可以实现公平锁
⑦lock可以实现读写锁
2. Atomic(1)对数据进行操作时(使用CAS来保证)可以保证原子性
(2)存在CAS的ABA问题
①使用AtomicStampedReference的版本号机制来管理(3)通过unsafe实现的包装类
①Unsafe底层实际上是调用C代码,C代码调用汇编,最后生成出 一条CPU指令cmpxchg,完成操作
② CompareAndSwap...()
(4) JDK增加了 LongAdder等四个类,高并发时效率更高,但是消 耗空间更多
3. AQS
(1)一个抽象的队列式的同步器,定义了一套多线程访问共享资 源的同步器框架(实现锁的框架),许多同步类实现都依赖于它,本 质上其实就是对于state的获取和释放
(2) ReentrantLock/ReentrantReadWriteLock/Semaphore/ CountDownLatch的实现都依赖于它(3)数据结构
①共享资源 volatile int state
②默认实现了 FIFO线程等待队列,底层是双向链表(4)线程模式
①独享模式
1) tryAcquire(int)/tryAcquireShared(int)
②共享模式
1) tryRelease/tryReleaseShared(int)
③既可独占,也可共享(ReentrantReadWriteLock)
4.线程池相关callable
①一般和ExecutorService配合来使用
②有返回值future
①判断任务是否完成
1) future.isDonef)
②返回执行结果
1) future.get()
③中断线程的执行
1) future.cancel))futureTask
①future接口的唯一实现类
②既可以作为Runnable被线程执行,又可以作为Future得到 Callable的返回值。
5. CAS算法
(1) CAS需要有3个操作数:内存地址V,旧的预期值A,即将要 更新的目标值B。,CAS指令执行时,当且仅当内存地址V的值与预期 值A相等时,将内存地址V的值修改为B,否那么就什么都不做。整个 比拟并替换的操作是一个原子操作。
①使用AtomicStampedReference的版本号机制来管理ABA问题
①如果内存地址V初次读取的值是A,并且在准备赋值的时候检查 到它的值仍然为A,但可能期间它已经被修改过了
②解决方法:使用AtomicStampedReference的版本号机制来管理
(3)通常将CAS用于同步的方式是从地址V读取值A,执行多步计 算来获得新值B,然后使用CAS将V的值从A改为B。如果V处的值尚 未同时更改,那么CAS操作成功。
(3) CAS操作是一条CPU指令,不会被打断,所以是原子操作
三、锁分类
1 .乐观锁,悲观锁(1)乐观锁
①CAS算法(2)悲观锁
① synchronized
2 .独享锁,共享锁
3 .公平锁,非公平锁
4 .可重入锁(1)线程得到一个对象锁后再次请求该对象锁,是允许的
(2)实现方法
①为每一个锁关联一个获取计数器和一个所有者线程,在获取锁的 时候判断owner,如果是相同的owner,那么count加1
5 .分段锁(1)通过分段锁的形式来细化锁的粒度,从而实现高效的并发操
作,例如 concurrentHashMap
6 .锁优化(1)偏向锁
①CAS操作:将线程ID保存在对象的Mark Word中
1)如果成功,那么说明该线程已经获取了对象的偏向锁
②有其他线程获取对象锁时失效
1)对象锁定时膨胀为轻量级锁
2)对象未锁定时恢复到未锁定状态
③经验依据:在大多数情况下,锁不仅不存在多线程竞争,而且总 是由同一线程屡次获得(2)轻量级锁
①CAS操作:将对象的Mark Word更新为指向Lock Record的指针
1)如果成功,那么该线程已经获取了对象的轻量级锁
2)如果失败,检查对象的Mark Word是否指向当前线程
a.如果指向当前线程,那么进入同步代码块
b.如果没有,那么膨胀为重量级锁
②如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为 重量级锁。
③经验依据:对绝大局部的锁,在整个同步周期内都不存在竞争(3)重量级锁
(4)自旋锁
①尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获 取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPUo(5)锁粗化
①如果一串零碎的操作都对同一个对象加锁,将会把加锁同步的范 围扩展(粗化)到整个操作序列的外部(6)锁消除
①对于一些代码上要求同步,但实际上并不需要同步的锁进行消除
四、ThreadLocal
1 . ThreadLocal提供了线程的局部变量,且不会和其他线程的局部 变量进行冲突,实现了线程的数据隔离。
2 .基本用法set(value)
① 首先获取当前thread的ThreadLocalMap
1)如果map已经初始化,那么将kv存入map中
2)否那么初始化map(此时构造函数已经将kv存入)get()
(1) removedinitialValue()
3 .底层实现ThreadLocalMap ThreadLocal 的内部类
(1) set():向 ThreadLocalMap 设置值,key 是 ThreadLocal 对象, 值是传递进来的对象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项。如果创立ThreadLocal的线 程一直持续运行,那么这个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.7
1) segment
a.继承了 ReentrantLock
b. 一个 concurrentHashMap 包含了一个 segment 数组
展开阅读全文