并发编程
2021-11-12 11:18:09   0  举报             
     
         
 AI智能生成
  针对于Java语言设计的多线程,以及并发编程的常用方式
    作者其他创作
 大纲/内容
  线程    
     创建    
     继承Thread  
     实现Runnable  
     匿名内部类  
     Callable和Feature    
     底层LockSupport    
     park()/unpark()  
     状态(虚拟机中状态)-不反应任何操作系统的线程状态
    
     新建  
     运行(Runnable)---(非Java中可再划分为就绪和运行)    
     调用yield,放弃当前cpu时间片,重新进入到就绪,等待被CPU调用  
     等待    
     配合Synchronize使用,调用wait方法后,释放锁,该线程存放在“waitThread”集合中等待;一旦调用notify的时候,该线程会重新进入到“blockThread”集合,同时状态会变为就绪,竞争锁的资源  
     超时等待    
     Sleep指令    
     睡眠时不释放“锁标志”  
     wait(long time)  
     join(long time)    
     底层使用wait/notify指令  
     阻塞    
     Synchronize锁中未获取到锁的线程,会存放到一个blockthreads集合中,一旦释放锁后,该集合中的线程又重新竞争锁对象<br>  
     终止    
     stop()指令    
     线程不安全,清除监控器锁的信息
  
     interrupt()指令    
     处于wait、join、sleep时可以生效  
     自定义volatile flag标志位  
     分类    
     用户线程  
     守护线程    
     跟随主线程停止而停止  
     安全问题    
     字节码  
     上下文切换  
     JMM内存模型  
     ThreadLocal    
     解说    
     ThreadLoca设置值    
     ThreadLocal从当前线程中获取到对应的threadLocalMap,然后给Map设定值(Entity<threadLocal,值>)  
     引用    
     ThreadLocal引用(两处)    
     栈中的引用  
     ThreadLocalMap中的Entry对象对应的key引用  
     类型    
     强、软、弱、虚    
     软,发生GC的时候,如果内存不过用则会回收  
     弱,发生GC的时候,会被回收  
     内存泄露    
     解说    
     ThreadLocal即便为null;但是堆内存中的ThreadLocal对象也存在,此时如果触发了Gc,然后会把Entry中的弱引用指向堆中的对象移除,此时Entry对应的key指向Null,但是Entry对象也不会被回收的  
     预防    
     方法一,先调用remove,然后再调用赋值ThreadLocal==null  
     方法二,ThreadLocal中set的时候会遍历之前的key,如果是null的话,则直接干掉  
     ThreadPool(线程池)    
     创建方式    
     四种(底层采用无界队列)  
     防止CPU飙高问题    
     底层从队列中获取任务采用超时的poll(),以防线程一直运行导致CPU飙高的问题  
     核心参数    
     核心数量  
     任务队列  
     最大线程数  
     存活时间    
     超出核心线程数好后创建的线程存活时间  
     线程工厂    
     线程池内部创建线程所用的工厂  
     处理器    
     任务无法执行时的处理器  
     状态    
     RUNNING    
     线程池可以接收新任务,及时对新添加的任务进行处理  
     SHUTDOWN    
     线程池不可以接收新任务,可以对已添加的任务进行处理  
     STOP    
     线程池不接收新任务,不处理已添加的任务,中断正在处理的任务  
     TIDYING    
     当所有的任务已终止,ctl记录的“任务数量”为0,线程池变为TIDYING,同时执行构造函数terminated()  
     TERMINATED    
     线程池彻底终止的状态  
     优化配置    
     CPU密集型    
     核心线程数=CPU核心数  
     IO密集型    
     核心线程数=CPU核心数*2  
     BlockQueue(队列)    
     ArrayBlockQueue    
     原理    
     底层采用Lock锁机制,进行元素的添加/移除,使用同一把锁  
     默认是有界限的,采用int进行计数的  
     LinkBlockQueue    
     原理    
     底层采用Lock锁机制,进行元素的添加/移除,使用不同锁  
     默认是无界限的,采用AutomicInteger计数的  
     Java内存模型    
     前因    
     CPU访问主内存的效率不高,因此引入了CPU高速缓存(工作内存);CPU ->CPU高速缓存->主内存    
     CPU高速缓存    
     L1  
     L2  
     L3  
     volatile    
     特征    
     线程之间数据的的可见性    
     缓存行(加入validate关键字)    
     CPU将数据从主内存读取到工作内存的时候,采用的是缓存行的方式,一次性读取64字节  
     伪共享问题    
     填充对象的属性到64字节    
     代码  
     注解  
     程序执行的顺序性(禁止重排序)  
     不保证原子性  
     底层    
     内存屏障    
     读内存    
     读指令前插入读屏障,高速缓存中数据无效,重新从主内存加载数据  
     写内存    
     写指令之后插入写屏障,写入缓存中的最新值刷新到主内存中  
     工作内存和主内存数据同步方式    
     通过汇编lock前缀指令触发底层锁的机制  
     锁的机制分类    
     总线锁    
     过程省略了-反正就是加锁的机制  
     MESI缓存一致性协议  
     扩充    
     全局共享变量存放在主内存中  
     Synchronized    
     线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量是需要从主内存总重新读取最新的值  
     线程解锁前,必须把共享变量的最新值刷新到主内存中  
     单例模式    
     创建对象的方式    
     new对象  
     克隆方式  
     反射  
     序列化与反序列化  
     说明    
     反射可以破解懒汉式、饿汉式;无法破解静态代码块;序列化和反序列化可以破解静态代码块(懒汉和饿汉也可以破解)  
     分类    
     懒汉式    
     普通  
     安全  
     双重校验锁    
     建议私有变量中添加volatile关键字,禁止new对象重排序  
     对象创建过程    
     1.分配内存空间  
     2.初始化对象  
     3. 内存空间的地址赋值给对应引用  
     2和3有可能被处理器优化,发生重排序  
     饿汉式    
     可以防御反序列化生成的对象
  
     备注    
     前提添加此方法readResolve(),JDK源码重新编译则依旧防御不了(Jdk源码判断反序列化类中如果存在readResolve方法,则通过反射机制调用readResolve方法返回相同的对象)  
     静态代码块    
     可以防御反射方式破解单例  
     备注    
     提前在该类的私有方法里面写一个判断If(对象!=nulll){throw new exception}  
     枚举    
     说明    
     反编译后,该类默认继承了Enum类,一些方法也是来源于此Enum类中的  
     可以防御反射    
     反射newInstance方法中会判断实例化类的类型是否是枚举,报错提示  
     可以防御序列化    
     序列化中获取对象的时候有个判断是否是枚举类,是的话,则直接调用枚举的valueOf方法获取枚举对象  
     FutureTask  
     ForkJoin  
     Disruptor  
     Callableball和FuturetureTask    
     实现方式    
     wait/notify(配合synchronized使用,阻塞当前调用线程)  
     LockSupport.park/unpark(阻塞当前调用的线程)
  
     锁    
     悲观/乐观  
     公平/非公平  
     CAS    
     上层封装框架    
     Lock类  
     下层依赖对象    
     unsafe    
     valueoffset    
     value在该对象中的偏移量,也就是该对象中填充的value属性对应的偏移位置  
     value  
     原理    
     底层把预期值N赋给V的时候,从CPU硬件级别保证了安全性  
     赋值失败的话,则重新循环,然后再做修改  
     范例    
     原子类-Atomic  
     问题    
     ABA问题    
     第一个线程修改值为A,第二个线程修改为B,第三个线程又修改为A  
     优化    
     增加版本号/递增时间戳  
     Synchronized    
     对象的组成    
     对象头-详解    
     Mark Word    
     8字节  
     包含内容    
     哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等  
     重点    
     GC分代年龄(4位)    
     因此GC之后的年龄最大是15岁  
     哈希码(31位)  
     锁标志位(3位)  
     未被使用的(26位)  
     Class指针    
     8字节(默认虚拟机开启指针压缩-占4个字节)  
     描述    
     对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例  
     实例数据    
     类中的属性等大小  
     填充    
     8的整数倍  
     Mark World中锁的状态    
     无锁-001    
     hash code(31),年龄(4)  
     偏向-101    
     线程ID(54),时间戳(2),年龄(4)  
     轻量级-000    
     栈中锁记录的指针(64)    
     此指针指向了线程栈中的LockRecord;LockRecord存放着无锁状态的值(HashCode,age,锁状态等)  
     重量级-010    
     monitor的指针(64)    
     Monitor记录了HashCode的值  
     GC标记-011    
     空,不需要记录信息
  
     方式    
     方法内锁对象==方法加Synchronized  
     方法内锁类字节码==静态方法加Synchronized  
     原理    
     jdk<1.6时候    
     通过Monitor对象调用操作系统的Mutex指令,来保证互斥量;属于OS自身来进行维护
  
     Jdk>1.6时候    
     增加了偏向锁、轻量级、重量级    
     因为考虑到可能锁住的代码块,执行效率可能会比较高,那么没必要让其它线程直接变成阻塞状态;所以可以在自旋里面重试一段时间,实在不行的话,再进入到阻塞状态  
     底层指令    
     monitor(监视器)对象-(每个同步对象都有自己的监视器锁)    
     Owner(拥有锁的线程)  
     Recursions(重入次数)  
     waitSet(等待池)    
     获取锁的对象,释放该对象锁后进入地方;当被其它线程notify的时候,则从等待池转到锁池中  
     entryList(锁池)    
     没有获取到锁的对象,通过链表数据结构存放  
     monitorenter    
     尝试获取对象所对应的monitor所有权,也就是尝试获取对象的锁  
     monitorexit    
     释放monitor的所有权  
     异常退出时也会执行  
     锁的升级过程    
     偏向锁    
     解说    
     场景    
     1.new object对象  
     2.Synchronized(对象)  
     现象    
     正常现象(没开启偏向锁)    
     先打印是无锁的状态,然后直接到了轻量级锁的状态,缺少偏向锁的升级过程  
     原因    
     默认情况下,JVM会推迟偏向锁的显示;因为考虑到启动JVM的时候,存在内部的线程使用Sync来执行操作  
     睡眠5s(开启偏向锁的时候)    
     先打印是偏向锁(预备状态,线程id为空),然后Synchronized中也是偏向锁状态,只不过此时会记录偏向哪个线程了  
     备注    
     偏向锁的阶段打印对象的HashCode的时候会直接把偏向锁的状态变成轻量级锁  
     轻量级锁(用户态)    
     原理    
     1.多个线程同时竞争同一把锁,通过CAS修改对象头中锁的状态    
     成功    
     对象头中HashCode值和栈帧中的LockRecord对象中地址值更换  
     扩充    
     线程执行的每个方法都会初始化成一个方法栈帧,该栈帧中存放着一个LockRecord对象    
     lockRecord(记录地址作用)    
     在加锁过程中将自身的地址和对象头中MarkWorld里面对应的内容做替换  
     objectReference    
     指向当前加锁对象的一个引用  
     锁的重入性    
     锁的重入后,则会新增一条Lockrecord作为重入次数;当退出代码块的时候,发现LockRecord地址指向的是null,则代表重入了,此时计数减一  
     2.当我们使用轻量级锁,释放锁时,还原MarkWord值内容  
     重量级锁    
     原理    
     1.没有获取到锁的线程,存放在C++ Monitor对象EntryList集合中,同时当前线程阻塞,释放CPU的执行权  
     特征    
     粗化    
     每个线程持有锁的时间尽可能短  
     消除    
     编译器级别一种锁优化方式  
     AQS(抽象同步队列)    
     依赖机制    
     CAS  
     自旋  
     LockSupport  
     Queue  
     Lock    
     核心参数    
     Node节点    
     双向链表中存放阻塞队列实体    
     waitStatus    
     1-当前的线程被取消  
     0-表示当前节点在sync队列中,等待着获取锁  
     -1-释放资源后需唤醒后继节点  
     -2-等待Condition唤醒,存放在等待池中  
     -3-工作与共享锁状态,需要想后传播  
     Thread    
     等到锁的线程  
     Head    
     头结点,等待队列的头结点  
     Tail    
     尾结点,正在等待的线程  
     state    
     锁的状态(0-无锁,1-有线程获取到锁)    
     如果线程重入的话,则state不断+1  
     exclusiveOwnerThread    
     记录当前持有锁的线程  
     特征    
     ReentrantLock 在默认情况下就是属于非公平锁    
     细节    
     公平和非公平就是在CAS的时候是否执行了!hasQueuedPredecessors()  
     Condition    
     等待池  
     Lock    
     阻塞列表,也就是锁池  
     分类    
     非公平锁    
     获取锁    
     使用CAS修改AQS中状态值(从0到1)    
     成功    
     则使用exclusiveOwnerThread记录当前线程  
     失败    
     初始化链表,将当前线程存放在AQS类的双向链表尾部    
     注意    
     头节点不存放任何线程    
     原因    
     本身AQS类中已经存在一个属性存放获取锁的线程,因此没必要在队列中再次存放  
     阻塞线程使用lockSupport.park  
     释放锁    
     当前AQS的状态如果为0,则会唤醒阻塞队列中头结点的下一个节点从新进入到竞争锁的状态,成功的话,则会移除  
     公平锁    
     区别(获取锁过程)    
     公平锁中首先会判断是否已经有等待的线程,如果没有等待的线程才开始利用CAS进行争抢  
     非公平锁刚开始获取锁的时候,直接使用了一次CAS尝试获取锁,不成功才会构建Node节点  
     释放锁过程一样  
     Condition    
     唤醒等待中的线程    
     调用signal()后,将等待池中的node节点转移存放在锁池,等待竞争锁资源  
     调用signal()后,再调用unlock()方法后,唤醒锁池中的线程开始竞争锁资源  
     AQS并发框架    
     Semaphore(信号箱)    
     1.初始化时给AQS类中的状态设置值  
     2.调用acquire()底层修改AQS中状态值-1的操作,如果修改成0之后,则当前线程阻塞,存放在AQS类中的双向链表中  
     3.调用release()底层对AQS类中状态+1的操作,同时唤醒AQS类中阻塞链表中首个线程  
     countdownLatch    
     1.初始化时给AQS类中的状态设置值  
     2.调用await(),阻塞当前线程,并放在双向链表中  
     3.调用countDown(),对AQS类中的状态值-1操作,如果状态==0    
     是    
     唤醒双向链表中存放的线程  
     CyclicBarrier(线程栅栏)    
     1.初始化给内部count 属性赋值  
     2.调用await()底层的时候,进行count-1操作,判断count!=0    
     是    
     存放在等待池中  
     否    
     继续执行,同时唤醒等待池中所有的线程  
     问题    
     Sync和Lock锁区别    
     前者是关键字,后者是接口与  
     前者无法判断是否获取锁的状态,后者可以  
     前者会自动释放锁,后者需要手动的,并且注意死锁  
     前者非公平的,后者是公平的  
    
 
 
 
 
  0 条评论
 下一页
  
   
   
   
   
  
  
  
  
  
  
  
  
  
  
 