多线程

Q:什么是ThreadLocal?是如何实现的?

线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域 ThreadLocal的数据结构

//threadLocalHashCode的值是HASH_INCREMENT = 0x61c88647每次+1,取0x61c88647是因为这个值是黄金分割点
//每个线程有唯一的一个ThreadLocalMap,每个ThreadLocalMap中可以存放多个ThreadLocal对象
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY]; //table是一个弱引用对象数组
    //INITIAL_CAPACITY=16
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//有hash证明这是个采用哈希散列方式进行存储
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);//设置扩容因子为len * 2 / 3
}

cleanSomeSlots:启发式清理,试探的扫描一些单元格,寻找过期元素,也就是被垃圾回收的元素 expungeStaleEntry:探测式清理,是以当前遇到的 GC 元素开始,向后不断的清理。直到遇到 null 为止,才停止 rehash 计算

Q:ThreadLocal是如何做到跟Thread绑定的?

在set中会对当前线程ThreadLocalMap进行处理 在get中会获取当前线程ThreadLocalMap中存储的值,没有就返回默认值

Q:ThreadLocal造成内存泄漏的原因?如何解决?

image 若堆中ThreadLocal对象会被gc回收,Entry的key为null,但是value不为null,且value也是强引用(连线6),所以Entry仍旧不能回收,只能释放ThreadLocal的内存,仍旧可能导致内存泄漏。在没有自动清理陈旧Entry的前提下,即使Entry使用弱引用,仍可能出现内存泄漏。 在threadlocal进行set、get、remove时都会进行内存回收操作,这样就避免了内存泄漏

Q:如何保证线程的处理是安全的?

线程同步,使用synchroinized或者ReentrantLock

Q:JAVA中的锁有哪些处理方式?

image

Q:java虚拟机中对象头的锁结构是什么样的?

image

Q:synchroinized是如何进行锁升级的?

image

Q:线程的生命周期?

image

Q:启动一个thread有哪几种方法?有什么区别?

  1. 实现Runnable接口,支持多继承
  2. 实现Callable接口,支持多继承,支持获取线程返回值
  3. 继承Thread类

Q:yield和sleep有什么异同?

两者都不会释放锁,yield只会使同优先级或者更高优先级的线程得到执行机会

Q:interrupted在阻塞态跟非阻塞态不同表现?

阻塞态:抛出InterruptedException,同时调用interrupte方法,此时isInterrupted返回true,然后被重置为false 非阻塞态:会调用interrupted方法

Q:为什么不建议使用stop停止线程?

thread.stop()调用之后,创建子线程的线程就会抛出ThreadDeatherror的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性

Q:多线程三要素?

  • 原子性(Atomicity):单个或多个操作是要么全部执行,要么都不执行
    • Lock:保证同时只有一个线程能拿到锁,并执行申请锁和释放锁的代码
    • synchronized:对线程加独占锁,被它修饰的类/方法/变量只允许一个线程访问
  • 可见性(Visibility):当一个线程修改了共享变量的值,其他线程能够立即得知这个修改
    • volatile:保证新值能立即同步到主内存,且每次使用前立即从主内存刷新;
    • synchronized:在释放锁之前会将工作内存新值更新到主存中
  • 有序性(Ordering):程序代码按照指令顺序执行
    • volatile: 本身就包含了禁止指令重排序的语义
    • synchronized:保证一个变量在同一个时刻只允许一条线程对其进行lock操作,使得持有同一个锁的两个同步块只能串行地进入

Q:synchronized即可修饰非静态方式,也可修饰静态方法,还可修饰代码块,有何区别?

  • 使用this修饰代码块,同步的同一个对象对代码块的多线程访问
  • 修饰非静态方法,同步的是同一个对象对这个方法的多线程访问
  • 修饰静态方法,同步的是这个类所有对象对这个方法的多线程访问

Q:造成死锁的原因?

  • 互斥条件:一个资源每次只能被一个线程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

Q:voliate原理?

如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存

  • Lock前缀的指令会引起处理器缓存写回内存;
  • 一个处理器的缓存回写到内存会导致其他处理器的缓存失效;
  • 当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值。

Q:CAS是什么?有什么问题?

CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。 CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作

  • 循环时间长开销很大。
  • 只能保证一个共享变量的原子操作。
  • ABA问题。使用AtomicStampedReference

Q:线程池原理?有几种运行状态?

image image

  • RUNNING:运行状态,接受新的任务并且处理队列中的任务。
  • SHUTDOWN:关闭状态(调用了shutdown方法)。不接受新任务,,但是要处理队列中的任务。
  • STOP:停止状态(调用了shutdownNow方法)。不接受新任务,也不处理队列中的任务,并且要中断正在处理的任务。
  • TIDYING:所有的任务都已终止了,workerCount为0,线程池进入该状态后会调 terminated() 方法进入TERMINATED 状态。
  • TERMINATED:终止状态,terminated() 方法调用结束后的状态。

Q:Worker 的实现类,为什么不使用 ReentrantLock 来实现呢,而是自己继承AQS?

tryAcquire方法它是不允许重入的,而ReentrantLock是允许重入的。对于线程来说,如果线程正在执行是不允许其它锁重入进来的。 线程只需要两个状态,一个是独占锁,表明正在执行任务;一个是不加锁,表明是空闲状态。

Q:AQS锁有哪些?各有什么特点?

锁的处理分为了两部分,一部分为如何加解锁,另一部分为把锁分配给谁,AQS就是为了解决把锁分配给谁的问题 AQS:AbstractQueuedSynchronizer,队列同步器

  • ReentrantReadWriteLock 读写锁,读锁是共享锁、写锁是独占锁
  • ReentrantLock 可重入锁,其内部类Sync继承AQS
  • 当锁被线程持有,AQS询问是否加锁成功时,Sync如果发现申请的线程与持有锁的线程是同一个,它将通过CAS更新state状态再次分配锁,并回复加锁成功。也就实现了重入。
  • 是否公平体现在,在向AQS申请分配锁时,有一次询问是否加锁成功的机会,在此时是否忽略CLH队列中等待的线程,就代表了是否给予插队的机会
  • CountDownLatch 闭锁
  • Semaphore 信号量锁,主要用于控制流量 ,new Semaphore(2, false);

Q:AQS如何实现的?

  1. 当申请锁,即调用了与acquire()类似语义的方法时,AQS将询问子类是否上锁成功,成功则继续运行。否则,AQS将以Node为粒度,记录这个申请锁的请求,将其插入自身维护的CLH队里中并挂起这个线程。
  2. 在CLH队列中,只有最靠近头节点的未取消申请锁的节点,才有资格申请锁。
  3. 当线程被唤醒时,会尝试获取锁,如果获取不到继续挂起;获取得到则继续运行。
  4. 当一个线程释放锁,即调用release()类似语义的方法时,AQS将询问子类是否解锁成功,有锁可以分配,如果有,AQS从CLH队列中主动唤起合适的线程
  5. 如果需要等待条件满足再去申请锁,即调用了wait()类似语义的方法时,在AQS中表现为,以Node为粒度,维护一个单向等待条件队列,把Node所代表的线程挂起。
  6. 当条件满足时,即调用了signal()类似语义的方法时,唤醒等待条件队列最前面的未取消等待的Node
  7. 子类可以维护AQS的state属性来记录加解锁状态,AQS也提供了CAS的方法compareAndSetState()抢占更新state。

Q:CountDownLactch与CyclicBarrier区别?

CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用

Q:Java中锁的类型?

image

  • 可重入锁:可重入锁又名递归锁,直指同一个线程在外层方法获得锁之后,在进入内层方法时,会自动获得锁。可重入锁的好处之一就是在一定程度上避免死锁

Q:锁优化都有哪些?

  • 自旋锁:自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态,但是它需要进行忙循环操作占用 CPU 时间,它只适用于共享数据的锁定状态很短的场景
  • 锁消除:是指对于被检测出不可能存在竞争的共享数据的锁进行消除。
  • 锁粗化:如果虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁,将会把加锁的范围扩展(粗化)到整个操作序列的外部
  • 轻量级锁:轻量级锁是相对于传统的重量级锁而言,它使用 CAS 操作来避免重量级锁使用互斥量的开销

Q:Synchronized的优化

  • 偏向锁
  • 轻量级锁
  • 重量级锁