Java八股文-并发学习记录2


Java多线程

1. Java中线程的实现方式

1)继承Thread类,重写run方法

2)实现Runnable接口,重写run方法

3)实现Callable 重写call方法,配合FutureTask。可以得到返回值。

4)基于线程池构建任务

Thread本身就implement了Runnable接口,FutureTask也是如此

2.Java中线程的状态

经典6种

3.Java中如何停止线程

1.使用共享变量 while(flag) ,不过很少使用

2.使用interrupt方式。在线程内部有一个中断标记位,默认为false;执行了以后其会变为true;

Thread.currentThread().isInterrupted()

Thread.currentThread.interrupt()

Thread.interrupted(); 先获取中断标记位,再把中断标记位归位为false

在线程休眠的时候,如果中断标记位变化,会抛出异常。

while(!Thread.currentThread().isInterrupted())

线程内部是可以正常执行的。

4.Java中sleep和wait的区别

sleep属于Thread类,是个静态方法。wait属于Object类的方法

sleep属于TIMED_WAITING,自动被唤醒。wait属于WAITING,需要手动唤醒。

wait必须在持有锁时执行,执行后会释放锁资源。sleep不会释放锁资源,也不需要持有锁

5.并发编程的三大特性

原子性,可见性,有序性。

可见性:CPU->L1->L2 ->L3—>主内存。 volatile强制到主内存去读和写。

有序性:JIT优化,或者CPU优化,造成指令重排。volatile内存屏障

6.什么是CAS,有什么优缺点

AtomicInteger这种原子类,就是用了CAS,里面是一个for循环。

并发量很大会一直循环,造成CPU资源消耗。 ABA问题(除了原值外,还要比较版本信息AtomicStampedReference)。

LongAdder,分成多个值,最后再加在一起。

CAS避免了像悲观锁那种用户态和内核态的切换。

7. @Contended注解有什么用

LongAddr中的cell数组,Cell就用了@Contended注解

这个注解是为了解决伪共享问题,解决缓存行同步带来的性能问题。

CPU缓存L1,是以缓存行为单位存储数据的,一般默认64字节。

@Contended注解,就是将一个缓存行后面填充7个没有意义的数据。

8. Java中的四种引用

强,软,弱,虚。

强引用。new User(); 始终处于可达状态,直到弹栈。

软引用。SoftReference,系统内存不够了,就回收软引用。在内存敏感的系统中当缓存使用。

弱引用,对于只有弱引用的对象,只要JVM执行垃圾回收,就会被回收。

虚引用,用不到。

9.ThreadLocal的内存泄露问题

ThreadLocalMap<ThreadLocal,value>

key为弱引用。可以解决内存泄露问题(ThreadLocal1对象,左边是强引用,右边的引用改为弱引用,等左边的消失了,右边的引用自动会消失)

value,没有Key,找不到。执行remove方法,移除Entry.

10.Java中锁的分类

可重入锁、不可重入锁。大部分都是前者

乐观锁,悲观锁。Atomic原子类基于CAS实现,是乐观锁。悲观锁会将线程挂起,涉及用户态和内核态的切换。

公平锁,非公平锁。

互斥锁,共享锁。ReentrantReadWriteLock是共享锁

11.Synchronized在JDK1.6中的优化

锁消除:这段代码没有临界资源,代码编译的时候直接把synchronized消除掉

锁膨胀:在一个循环中,频繁的获取锁资源。JIT会把锁扩到循环外层。
锁升级:在JDK6之前,拿不到锁就会挂起当前线程。之后是

无锁->偏向锁->轻量级锁->重量级锁

偏向级锁是如果只有一个线程频繁获取这个锁,那就用偏向锁判断一下是否是这个线程即可,不是的话就触发锁升级。

轻量级锁,会采用自旋锁的方式频繁的以CAS的形式获取锁资源。自旋了一定次数后,没拿到锁资源,则进行锁升级

重量级锁:最传统的synchronized方法,没拿到锁则挂起当前线程

12. synchronized的实现原理

对象头的MarkWord结构:

13. 什么是AQS

AQS就是AbstractQueuedSynchronized抽象类。JUC下的一个基类。

ReentrantLock,ThreadPoolExecutor,阻塞队列,CountDownLatch,Semaphore,CyclicBarrier等都是基于AQS实现

内部用volatile修饰了一个state的int整数。用CAS对他进行修改。

有一个双向链表。每一个Node可以被声明为是共享的还是互斥的,存储着线程信息。把线程封装成Node去双向链表进行排队。

ConditionObject(与Node同级别):线程被await后,会被扔到WaitSet池子里,被唤醒后再拿出来到AQS的双向链表。而ConditionObject里维护了一个双向链表用来当Waitset。

14.AQS唤醒节点时,为何从后往前找

unparkSuccessor()

因为插入的 时候是这么插的,A为被插入的节点。先指向前面,再有一个指向A的指针。

因为可能在下图这个阶段唤醒节点,这个时候还没有指向A的指针,从前往后找会遗漏A

15.ReentrantLock和synchronized的区别

unlock. synchronized只能是非公平锁。tryLock,指定时间,

竞争比较激烈的话用ReentrantLock,不存在锁升级。

ReentrantLock是基于AQS实现的,synchronized是基于ObjectMonitor.

16.ReentrantReadWriteLock的实现原理

state的高16位是读锁,低16位是写锁。可重入锁。写锁对state加一就好,读锁要用ThreadLocal来记录一下每个线程锁重入的次数。

17. JDK中提供了哪些线程池

×

18.线程池的核心参数有哪些

19.线程池的状态

在ThreadPoolExecutor中,AtomicInteger ctl是最核心的属性,ctl的高三位,表示线程池状态;ctl的低29位,表示工作线程的个数。

20.线程池的执行流程

21.线程池添加工作线程的流程

后面自己写个线程池!

22.线程池为何要构建空任务的非核心线程

1.在核心线程数为0的时候,需要这样。不然扔进阻塞队列没人管了。

2.allowCoreThreadTimeOut,如果把这个参数设置为true,则核心线程也会被超时干掉。

23.线程池使用完毕为何必须shutdown()

不然整个线程池对象ThreadPoolExecutor无法被回收,占用堆对象很严重。

24.线程池的核心参数到底如何设置

核心线程数:Hippo4j,监控。

25.concurrentHashMap在JDK1.8做的优化

引入红黑树。数组+链表。链表足够长会变为红黑树。

在没有hash冲突时,CAS锁头节点去put。

在出现hash冲突时,synchronized。

扩容的时候会有协助扩容。

计数器用addCount,与LongAdder极其相似。线程A对1位置++,线程B对2位置++,最后汇总。

弱一致性。A写B查,但是B确实不一定能查到,不保证这种复合情况下的强一致性。

26.concurrentHashMap的散列算法

存储操作putVal方法的散列算法:spread(key.hashCode()),

(h^(h>>>16)) & HASH_BITS

用传统的hashCode做运算,只有低位的一些可以参与运算。而>>>16后,高位的也可以参与运算。这样可以尽可能的打散数据。

(n-1) &hash 其中n必须是2的整数次方。

27.concurrentHashMap的初始化数组的流程

初始化是懒加载的。类似于单例模式的两把锁,避免多个线程重复去初始化。

28.concurrentHashMap扩容的流程

基于老数组的长度计算一个扩容标识戳,如果两个线程一致,则可以协助扩容。

29.concurrentHashMap读取数据的流程

没有锁。基于key得到hashCode值,然后定位到合适位置,再去find,根据具体的数据结构去做查找。

30.concurrentHashMap中计数器的实现

addCount(),如果用AtomicInteger,并发比较大会让每个线程去执行CAS,不太合适。

CounterCell[] as,基于LongAdder的思想。


文章作者: 爱敲代码の鱼儿
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 爱敲代码の鱼儿 !
  目录