java基础知识个人理解

java基础知识个人理解

为了方便自己快速的回忆java相关基础知识

1.hashmap

java7中Hashmap在并发下有死循环、put或get方法并发问题。
死循环是因为hashmap在多线程同时扩容的时候,每个线程各自初始化新的table,再把原先的节点使用头插法链到新的table上,最终会导致节点间出现循环依赖,这样在get方法的时候死循环。java8中使用尾插法,解决了该问题。
java8的hashmap为了解决get方法复杂度为O(N)的问题,在链表数量大于8时(要先判断hash桶是否大于64,如果不大于64则先进行扩容),会将该链表转化为红黑树,从而使复杂度降为O(log(N))。

java8的concurrenthashmap抛弃了java以前的锁分段的用法,使用CAS+Synchronized实现并发安全。
普通hashmap并发不安全的地方主要在三个地方
1.初始化
concurrenthashmap中有个属性sizeCtl,具体含义如下

  • 负数代表正在进行初始化或扩容操作
  • 1代表正在初始化
  • N 表示有N-1个线程正在进行扩容操作
  • 正数或0代表hash表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小

在一个线程已经初始化时,使用CAS把sizeCtl状态变更,其他线程感知到有线程在初始化,使用Thread.yield()将线程挂起来解决并发问题。

2.put方法

  • 如果table[i]要插入节点位置为Null,使用CAS把节点添加到table[i]上
  • table[i]不为Null并且hash值=-1,说明该节点为forward节点(已经扩容完毕的节点),说明当前正在扩容,去协助扩容。
  • 前两种情况都不满足,将table[i]节点使用Synchronized锁住,判断需不需要转变红黑树,再执行相应插入操作。

3.get方法
get方法完全无锁,通过hash计算要查找的table[i],再获取table[i]的hash值,如果值为-1,表明是forward节点,则调用该节点的find方法,在新的nextTable去查询节点。如果大于0,则在当前table[i]下寻找节点。

2.线程

1.线程池
在Executors类下,提前帮我们实现了四种线程池分别为
Executors.newFixedThreadPool(x)
Executors.newCachedThreadPool()
Executors.newScheduledThreadPool(5)
Executors.newSingleThreadExecutor()
返回值为ExecutorService
使用该方法创建出的newFixedThreadPool 和newCachedThreadPool使用的无界队列,也就是LinkedBlockingQueue,会发生OOM,所以不建议使用

2.ThreadPoolExecutor
上述四种线程池其实也是调用的该类的构造函数生成的,只不过Executors帮我们封装了相应参数。
完整构造参数如下

  • corePoolSize 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。
  • maximumPoolSize 线程池中线程的上限 超过该数量会使用拒绝策略
  • keepAliveTime 最大空闲时间,超过这个时间,多余的线程会被回收。
  • workQueue 任务队列 当任务队列为BlockingQueue时,如果核心线程都不可用,则会在队列放满时,再新增线程运行任务。当任务队列为SynchronousQueue时,总会新开线程执行任务。
  • RejectedExecutionHandler 当运行线程数大于maximumPoolSize时的拒绝策略 默认为抛出异常,提供了四种也可以自定义 1.抛出异常 2.直接忽略 3.丢弃最老的线程 4.由主线程执行
  • ThreadFactory threadFactory, 新线程的产生方式,也就是给线程起个名称什么的。

3.ExecutorService
ExecutorService为Executor直接的扩展接口(Executor只有excute方法)

  • ExecutorService.submit用于提交一个新线程到线程池中 ExecutorService.shutdown
  • 用于停止线程池中的线程(会让线程执行完) ExecutorService.shutdownNow
  • 用于停止线程池中的线程(返回未执行的线程列表,会发生线程中断异常InterruptedException)
  • ExecutorService.invokeAll 接受一个线程列表,当所有线程运行完成返回Futrue集合
  • ExecutorService.invokeAny 接受一个线程列表,当任一线程运行完成该线程的结果,取消其他正在进行的线程

3.G1

1.概述,g1整体上使用了复制算法,解决了CMS中的内存碎片问题,并把所有的堆内容分为了region,通过计算不同region的回收效率,根据指定的目标选择合适的region进行回收
2.region,region分为四种

  • E=Eden
  • S=Survivor 当E的对象移动到S时,如果空间不够,则全部转移到O中,并将E整体回收
  • O=Old
  • H=humongous 用于存放大对象(超过region 50%以上)

3.Remembered Set
对象之间的引用会不可避免的出现跨代引用的情况,对于old->young这种例子,在回收时young,必须要扫描old,开销太大,所以在每个region上定义了RS,RS存放的时有那些old region引用了本region的对象(谁引用了我),这样在进行扫描时,只需要把RS中的对象作为root即可。
4.young gc
Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发,整个回收阶段都要STW。

  • 阶段1:根扫描 静态和本地对象被扫描
  • 阶段2:更新RS 处理dirty card队列更新RS
  • 阶段3:处理RS 检测从年轻代指向年老代的对象
  • 阶段4:对象拷贝 拷贝存活的对象到survivor/old区
  • 阶段5:处理引用队列 软引用,弱引用,虚引用处理

5.full gc
由于full gc会先触发一次young gc 所以 初始标记与young gc的初始标记是可以复用的

  • 初始标记(STW 耗时很短) 在此阶段,G1 GC 对根进行标记。该阶段与常规的 (STW) 年轻代垃圾回收密切相关。
  • 根区域扫描(与用户线程并行执行) G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。
  • 并发标记(Concurrent Marking) G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断
  • 最终标记(STW) 最终确定标记(耗时长),清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理。
  • 清除垃圾(STW) 在这个最后阶段,执行统计和 RSet 净化的 STW 操作。在统计期间,G1 GC 会识别完全空闲的区域和可供进行混合垃圾回收的区域。清理阶段在将空白区域重置并返回到空闲列表时为部分并发。

由于存在与用户线程并发情况,所以引入了SATB(Snapshot-At-The-Beginning)机制,确保垃圾回收的正确性。在GC开始时,创建一个堆的对象引用快照,并使用了三色标记算法,来标记对象的引用状态。在并发标记阶段,如果对象的引用状态发生改变,则将这些对象置灰,表明对象不可回收,因此G1回收会产生浮动垃圾(float garbge),当浮动垃圾过多时,G1会退化使用serial回收堆空间。

6.三色标记算法

  • 黑色:根对象,或者该对象与它的子对象都被扫描
  • 灰色:对象本身被扫描,但还没扫描完该对象中的子对象
  • 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象

4.线程相关

1.线程状态

  • 新建状态(New):当线程对象对创建后,即进入了新建状态
  • 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,获取cpu 的使用权,并不是说执行了t.start()此线程立即就会执行
  • 运行状态(Running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。 当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
  • 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权
  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

2.线程调用方法

  • sleep():sleep为Thread提供的方法,执行该方法后,当前线程让出CPU(依然保留着资源,因此不能在同步代码中执行sleep方法)等待休眠时间结束后,重新竞争线程执行。
  • wait():wait为Object的方法,调用wati方法会释放掉线程的所有资源(包括锁),并且只能被Notify方法唤醒,唤醒后,先获取对象锁。
  • yield():与sleep方法相同,但区别是,只允许同优先级的线程获取CPU时间片。

你可能感兴趣的:(java基础知识个人理解)