title: Java 面试总结
date: 2020-06-04 13:51:00
categories:
- Java
- JavaSE
tags:
- Java
- JavaSE
- 面试
permalink: /pages/5f886e/
String 类能被继承吗?
String,StringBuffer,StringBuilder 的区别。
String 类不能被继承。因为其被 final 修饰,所以无法被继承。
StringBuffer,StringBuilder 拼接字符串,使用 append 比 String 效率高。因为 String 会隐式 new String 对象。
StringBuffer 主要方法都用 synchronized 修饰,是线程安全的;而 StringBuilder 不是。
抽象类和接口的区别?
类可以继承多个类么?接口可以继承多个接口么?类可以实现多个接口么?
类只能继承一个类,但是可以实现多个接口。接口可以继承多个接口。
继承和聚合的区别在哪?
一般,能用聚合就别用继承。
反射创建实例有几种方式?
通过反射来创建实例对象主要有两种方式:
Class
对象的 newInstance
方法。Constructor
对象的 newInstance
方法。加载实例有几种方式?
Class.forName("className") 和 ClassLoader.laodClass("className") 有什么区别?
Class.forName("className")
加载的是已经初始化到 JVM 中的类。ClassLoader.loadClass("className")
装载的是还没有初始化到 JVM 中的类。动态代理有几种实现方式?有什么特点?
JDK 动态代理和 CGLIB 动态代理有什么区别?
(1)JDK 方式
代理类与委托类实现同一接口,主要是通过代理类实现 InvocationHandler
并重写 invoke
方法来进行动态代理的,在 invoke
方法中将对方法进行处理。
JDK 动态代理特点:
InvocationHandler
接口。(2)CGLIB
CGLIB 底层,其实是借助了 ASM 这个强大的 Java 字节码框架去进行字节码增强操作。
CGLIB 动态代理特点:
优点:使用字节码增强,比 JDK 动态代理方式性能高。可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口。
缺点:不能对 final
类以及 final
方法进行代理。
有
==
运算符了,为什么还需要 equals 啊?说一说你对 java.lang.Object 对象中 hashCode 和 equals 方法的理解。在什么场景下需 要重新实现这两个方法。
有没有可能 2 个不相等的对象有相同的 hashcode
(1)有==
运算符了,为什么还需要 equals 啊?
equals 等价于==
,而==
运算符是判断两个对象是不是同一个对象,即他们的地址是否相等。而覆写 equals 更多的是追求两个对象在逻辑上的相等,你可以说是值相等,也可说是内容相等。
(2)说一说你对 java.lang.Object 对象中 hashCode 和 equals 方法的理解。在什么场景下需 要重新实现这两个方法。
在集合查找时,hashcode 能大大降低对象比较次数,提高查找效率!
(3)有没有可能 2 个不相等的对象有相同的 hashcode
有可能。
什么是 NIO?
NIO 和 BIO、AIO 有何差别?
序列化、反序列化有哪些问题?如何解决?
Java 的序列化能保证对象状态的持久保存,但是遇到一些对象结构复杂的情况还是难以处理,这里归纳一下:
Serializable
接口时,所有子类都可以被序列化。Serializable
接口,父类没有,则父类的属性不会被序列化(不报错,数据丢失),子类的属性仍可以正确序列化。Serializable
接口,否则会报错。serialVersionUID
被修改,则反序列化时会失败。ArrayList 是数组链表,访问效率更高。
LinkedList 是双链表,数据有序存储。
请描述 HashMap 的实现原理?
答:否。
要点:创建线程和线程上下文切换有一定开销。
说明:即使是单核处理器也支持多线程。CPU 通过给每个线程分配时间切片的算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保持上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
引申
我们可以使用 Thread
类的 Sleep() 方法让线程暂停一段时间。
需要注意的是,这并不会让线程终止,一旦从休眠中唤醒线程,线程的状态将会被改变为 Runnable,并且根据线程调度,它将得到执行。
线程调度器是一个操作系统服务,它负责为 Runnable
状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
时间分片是指将可用的 CPU 时间分配给可用的 Runnable
线程的过程。
分配 CPU 时间可以基于线程优先级或者线程等待的时间。线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。
上下文切换是存储和恢复 CPU 状态的过程,它使得线程执行能够从中断点恢复执行。上下文切换是多任务操作系统和多线程环境的基本特征。
volatile
关键字死锁是指两个以上的线程永远相互阻塞的情况,这种情况产生至少需要两个以上的线程和两个以上的资源。
分析死锁,我们需要查看 Java 应用程序的线程转储。我们需要找出那些状态为 BLOCKED 的线程和他们等待的资源。每个资源都有一个唯一的 id,用这个 id 我们可以找出哪些线程已经拥有了它的对象锁。
避免嵌套锁,只在需要的地方使用锁和避免无限期等待是避免死锁的通常办法。
java.lang.Thread.State
中定义了 6 种不同的线程状态,在给定的一个时刻,线程只能处于其中的一个状态。
以下是各状态的说明,以及状态间的联系:
start()
方法的线程处于此状态。start()
方法的线程状态。此状态意味着,线程已经准备好了,一旦被线程调度器分配了 CPU 时间片,就可以运行线程。Object.wait()
之后输入同步块/方法或重新输入同步块/方法。Object.wait()
Thread.join()
LockSupport.park()
Thread.sleep(sleeptime)
Object.wait(timeout)
Thread.join(timeout)
LockSupport.parkNanos(timeout)
LockSupport.parkUntil(timeout)
run()
方法执行结束,或者因异常退出了 run()
方法,则该线程结束生命周期。死亡的线程不可再次复生。👉 参考阅读:Java
Thread
Methods andThread
States 👉 参考阅读:Java 线程的 5 种状态及切换(透彻讲解)
创建线程主要有三种方式:
1. 继承 Thread
类
Thread
类的子类,并重写该类的 run()
方法,该 run()
方法的方法体就代表了线程要完成的任务。因此把 run()
方法称为执行体。Thread
子类的实例,即创建了线程对象。start()
方法来启动该线程。2. 实现 Runnable
接口
Runnable
接口的实现类,并重写该接口的 run()
方法,该 run()
方法的方法体同样是该线程的线程执行体。Runnable
实现类的实例,并以此实例作为 Thread
对象,该 Thread
对象才是真正的线程对象。3. 通过 Callable
接口和 Future
接口
Callable
接口的实现类,并实现 call()
方法,该 call()
方法将作为线程执行体,并且有返回值。Callable
实现类的实例,使用 FutureTask
类来包装 Callable
对象,该 FutureTask
对象封装了该 Callable
对象的 call()
方法的返回值。FutureTask
对象作为 Thread
对象的 target 创建并启动新线程。FutureTask
对象的 get()
方法来获得子线程执行结束后的返回值三种创建线程方式对比
Runnable
接口优于继承 Thread
类,因为根据开放封闭原则——实现接口更便于扩展;Runnable
接口的线程没有返回值;而使用 Callable
/ Future
方式可以让线程有返回值。👉 参考阅读:Java 创建线程的三种方式及其对比
Callable
和 Future
?什么是 FutureTask
?什么是 Callable
和 Future
?
Java 5 在 concurrency 包中引入了 Callable
接口,它和 Runnable
接口很相似,但它可以返回一个对象或者抛出一个异常。
Callable
接口使用泛型去定义它的返回类型。Executors
类提供了一些有用的方法去在线程池中执行 Callable
内的任务。由于 Callable
任务是并行的,我们必须等待它返回的结果。Future
对象为我们解决了这个问题。在线程池提交 Callable
任务后返回了一个 Future
对象,使用它我们可以知道 Callable
任务的状态和得到 Callable
返回的执行结果。Future
提供了 get()
方法让我们可以等待 Callable
结束并获取它的执行结果。
什么是 FutureTask
?
FutureTask
是 Future
的一个基础实现,我们可以将它同 Executors
使用处理异步任务。通常我们不需要使用 FutureTask
类,单当我们打算重写 Future
接口的一些方法并保持原来基础的实现是,它就变得非常有用。我们可以仅仅继承于它并重写我们需要的方法。阅读 Java FutureTask
例子,学习如何使用它。
start()
和 run()
有什么区别?可以直接调用 Thread
类的 run()
方法么?run()
方法是线程的执行体。start()
方法负责启动线程,然后 JVM 会让这个线程去执行 run()
方法。可以直接调用 Thread
类的 run()
方法么?
Thread
的 run()
方法,它的行为就会和普通的方法一样。start()
方法。sleep()
、yield()
、join()
方法有什么区别?为什么 sleep()
和 yield()
方法是静态(static)的?yield()
yield()
方法可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程从 Running 状态转入 Runnable
状态。yield()
方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。sleep()
sleep()
方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入 Blocked 状态。sleep()
方法不会释放“锁标志”,也就是说如果有 synchronized
同步块,其他线程仍然不能访问共享数据。join()
join()
方法会使当前线程转入 Blocked 状态,等待调用 join()
方法的线程结束后才能继续执行。为什么 sleep()
和 yield()
方法是静态(static)的?
Thread
类的 sleep()
和 yield()
方法将处理 Running 状态的线程。所以在其他处于非 Running 状态的线程上执行这两个方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。👉 参考阅读:Java 线程中 yield 与 join 方法的区别 👉 参考阅读:sleep(),wait(),yield()和 join()方法的区别
Java 中的线程优先级如何控制
[1,10]
,一般来说,高优先级的线程在运行时会具有优先权。可以通过 thread.setPriority(Thread.MAX_PRIORITY)
的方式设置,默认优先级为 5
。高优先级的 Java 线程一定先执行吗
什么是守护线程
为什么要用守护线程
如何创建守护线程
thread.setDaemon(true)
可以设置 thread 线程为守护线程。thread.setDaemon(true)
必须在 thread.start()
之前设置,否则会抛出 llegalThreadStateException
异常;👉 参考阅读:Java 中守护线程的总结
当线程间是可以共享资源时,线程间通信是协调它们的重要的手段。Object
类中 wait()
, notify()
和 notifyAll()
方法可以用于线程间通信关于资源的锁的状态。
👉 参考阅读:Java 并发编程:线程间协作的两种方式:wait、notify、notifyAll 和 Condition
wait()
, notify()
和 notifyAll()
被定义在 Object 类里?Java 的每个对象中都有一个锁(monitor,也可以成为监视器) 并且 wait()
、notify()
等方法用于等待对象的锁或者通知其他线程对象的监视器可用。在 Java 的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是 Object 类的一部分,这样 Java 的每一个类都有用于线程间通信的基本方法
wait()
, notify()
和 notifyAll()
必须在同步方法或者同步块中被调用?当一个线程需要调用对象的 wait()
方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()
方法。同样的,当一个线程需要调用对象的 notify()
方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。
由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
👉 参考阅读:Java 并发核心机制
synchronized
synchronized
有什么作用?
synchronized
的原理是什么?同步方法和同步块,哪个更好?
JDK1.6 对
synchronized
做了哪些优化?使用
synchronized
修饰静态方法和非静态方法有什么区别?
作用
synchronized
可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
synchronized
有 3 种应用方式:
Class
对象synchonized
括号里配置的对象原理
synchronized
经过编译后,会在同步块的前后分别形成 monitorenter
和 monitorexit
这两个字节码指令,这两个字节码指令都需要一个引用类型的参数来指明要锁定和解锁的对象。如果 synchronized
明确制定了对象参数,那就是这个对象的引用;如果没有明确指定,那就根据 synchronized
修饰的是实例方法还是静态方法,去对对应的对象实例或 Class
对象来作为锁对象。
synchronized
同步块对同一线程来说是可重入的,不会出现锁死问题。
synchronized
同步块是互斥的,即已进入的线程执行完成前,会阻塞其他试图进入的线程。
优化
Java 1.6 以后,synchronized
做了大量的优化,其性能已经与 Lock
、ReadWriteLock
基本上持平。
synchronized
的优化是将锁粒度分为不同级别,synchronized
会根据运行状态动态的由低到高调整锁级别(偏向锁 -> 轻量级锁 -> 重量级锁),以减少阻塞。
同步方法 or 同步块?
volatile
volatile
有什么作用?
volatile
的原理是什么?
volatile
能代替锁吗?
volatile
和synchronized
的区别?
volatile
无法替代 synchronized
,因为 volatile
无法保证操作的原子性。
作用
被 volatile
关键字修饰的变量有两层含义:
原理
观察加入 volatile 关键字和没有加入 volatile 关键字时所生成的汇编代码发现,加入 volatile
关键字时,会多出一个 lock
前缀指令。
lock
前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供 3 个功能:
volatile
和 synchronized
的区别?
volatile
本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized
则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。volatile
仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别的。volatile
仅能实现变量的修改可见性,不能保证原子性;而 synchronized
则可以保证变量的修改可见性和原子性volatile
不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。volatile
标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。什么是 CAS?
CAS 有什么作用?
CAS 的原理是什么?
CAS 的三大问题?
作用
CAS(Compare and Swap),字面意思为比较并交换。CAS 有 3 个操作数,分别是:内存值 V,旧的预期值 A,要修改的新值 B。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。
原理
Java 主要利用 Unsafe
这个类提供的 CAS 操作。Unsafe
的 CAS 依赖的是 JV M 针对不同的操作系统实现的 Atomic::cmpxchg
指令。
三大问题
ThreadLocal
ThreadLocal
有什么作用?
ThreadLocal
的原理是什么?如何解决
ThreadLocal
内存泄漏问题?
作用
ThreadLocal
是一个存储线程本地副本的工具类。
原理
Thread
类中维护着一个 ThreadLocal.ThreadLocalMap
类型的成员 threadLocals
。这个成员就是用来存储当前线程独占的变量副本。
ThreadLocalMap
是 ThreadLocal
的内部类,它维护着一个 Entry
数组, Entry
用于保存键值对,其 key 是 ThreadLocal
对象,value 是传递进来的对象(变量副本)。 Entry
继承了 WeakReference
,所以是弱引用。
内存泄漏问题
ThreadLocalMap 的 Entry
继承了 WeakReference
,所以它的 key (ThreadLocal
对象)是弱引用,而 value (变量副本)是强引用。
ThreadLocal
对象没有外部强引用来引用它,那么 ThreadLocal
对象会在下次 GC 时被回收。Entry
中的 key 已经被回收,但是 value 由于是强引用不会被垃圾收集器回收。如果创建 ThreadLocal
的线程一直持续运行,那么 value 就会一直得不到回收,产生内存泄露。那么如何避免内存泄漏呢?方法就是:使用 ThreadLocal
的 set
方法后,显示的调用 remove
方法 。
👉 参考阅读:全面理解 Java 内存模型
👉 参考阅读:Java 并发容器
什么是同步容器?
有哪些常见同步容器?
它们是如何实现线程安全的?
同步容器真的线程安全吗?
类型
Vector
、Stack
、Hashtable
作用/原理
同步容器的同步原理就是在方法上用 synchronized
修饰。 synchronized
可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
synchronized
的互斥同步会产生阻塞和唤醒线程的开销。显然,这种方式比没有使用 synchronized
的容器性能要差。
线程安全
同步容器真的绝对安全吗?
其实也未必。在做复合操作(非原子操作)时,仍然需要加锁来保护。常见复合操作如下:
请描述 ConcurrentHashMap 的实现原理?
ConcurrentHashMap 为什么放弃了分段锁?
基础数据结构原理和 HashMap
一样,JDK 1.7 采用 数组+单链表;JDK 1.8 采用数组+单链表+红黑树。
并发安全特性的实现:
JDK 1.7:
JDK 1.8:
transient volatile HashEntry<K,V>[] table
保存数据,采用 table 数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。synchronized
。
f
即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。hashcode == MOVED == -1
,则需要进行扩容。TREEIFY_THRESHOLD
则要转换为红黑树。CopyOnWriteArrayList 的作用?
CopyOnWriteArrayList 的原理?
作用
CopyOnWrite 字面意思为写入时复制。CopyOnWriteArrayList 是线程安全的 ArrayList。
原理
CopyOnWriteAarrayList
中,读操作不同步,因为它们在内部数组的快照上工作,所以多个迭代器可以同时遍历而不会相互阻塞(1,2,4)。
👉 参考阅读:Java 并发锁
Java 中有哪些锁?
这些锁有什么特性?
可重入锁
ReentrantLock
、ReentrantReadWriteLock
是可重入锁。这点,从其命名也不难看出。synchronized
也是一个可重入锁。公平锁与非公平锁
synchronized
只支持非公平锁。ReentrantLock
、ReentrantReadWriteLock
,默认是非公平锁,但支持公平锁。独享锁与共享锁
synchronized
、ReentrantLock
只支持独享锁。ReentrantReadWriteLock
其写锁是独享锁,其读锁是共享锁。读锁是共享锁使得并发读是非常高效的,读写,写读 ,写写的过程是互斥的。悲观锁与乐观锁
悲观锁在 Java 中的应用就是通过使用 synchronized
和 Lock
显示加锁来进行互斥同步,这是一种阻塞同步。
乐观锁在 Java 中的应用就是采用 CAS 机制(CAS 操作通过 Unsafe
类提供,但这个类不直接暴露为 API,所以都是间接使用,如各种原子类)。
偏向锁、轻量级锁、重量级锁
Java 1.6 以前,重量级锁一般指的是 synchronized
,而轻量级锁指的是 volatile
。
Java 1.6 以后,针对 synchronized
做了大量优化,引入 4 种锁状态: 无锁状态、偏向锁、轻量级锁和重量级锁。锁可以单向的从偏向锁升级到轻量级锁,再从轻量级锁升级到重量级锁 。
分段锁
分段锁其实是一种锁的设计,并不是具体的一种锁。典型:JDK1.7 之前的 ConcurrentHashMap
显示锁和内置锁
synchronized
ReentrantLock
、ReentrantReadWriteLock
等。什么是 AQS?
AQS 的作用是什么?
AQS 的原理?
作用
AbstractQueuedSynchronizer
(简称 AQS)是队列同步器,顾名思义,其主要作用是处理同步。它是并发锁和很多同步工具类的实现基石(如 ReentrantLock
、ReentrantReadWriteLock
、Semaphore
等)。
AQS 提供了对独享锁与共享锁的支持。
原理
(1)数据结构
state
- AQS 使用一个整型的 volatile
变量来 维护同步状态。
ReentrantLock
中该状态值表示所有者线程已经重复获取该锁的次数,Semaphore
中该状态值表示剩余的许可数量。head
和 tail
- AQS 维护了一个 Node
类型(AQS 的内部类)的双链表来完成同步状态的管理。这个双链表是一个双向的 FIFO 队列,通过 head
和 tail
指针进行访问。当 有线程获取锁失败后,就被添加到队列末尾。(2)获取独占锁
AQS 中使用 acquire(int arg)
方法获取独占锁,其大致流程如下:
(3)释放独占锁
AQS 中使用 release(int arg)
方法释放独占锁,其大致流程如下:
(4)获取共享锁
AQS 中使用 acquireShared(int arg)
方法获取共享锁。
acquireShared
方法和 acquire
方法的逻辑很相似,区别仅在于自旋的条件以及节点出队的操作有所不同。
成功获得共享锁的条件如下:
tryAcquireShared(arg)
返回值大于等于 0 (这意味着共享锁的 permit 还没有用完)。(5)释放共享锁
AQS 中使用 releaseShared(int arg)
方法释放共享锁。
releaseShared
首先会尝试释放同步状态,如果成功,则解锁一个或多个后继线程节点。释放共享锁和释放独享锁流程大体相似,区别在于:
对于独享模式,如果需要 SIGNAL,释放仅相当于调用头节点的 unparkSuccessor
。
什么是 ReentrantLock?
什么是可重入锁?
ReentrantLock 有什么用?
ReentrantLock 原理?
作用
ReentrantLock
提供了一组无条件的、可轮询的、定时的以及可中断的锁操作
ReentrantLock
的特性如下:
ReentrantLock
提供了与 synchronized
相同的互斥性、内存可见性和可重入性。ReentrantLock
支持公平锁和非公平锁(默认)两种模式。ReentrantLock
实现了 Lock
接口,支持了 synchronized
所不具备的灵活性。
synchronized
无法中断一个正在等待获取锁的线程synchronized
无法在请求获取一个锁时无休止地等待原理
ReentrantLock
基于其内部类 ReentrantLock.Sync
实现,Sync
继承自 AQS。它有两个子类:
ReentrantLock.FairSync
- 公平锁。ReentrantLock.NonfairSync
- 非公平锁。本质上,就是基于 AQS 实现。
ReentrantReadWriteLock 是什么?
ReentrantReadWriteLock 的作用?
ReentrantReadWriteLock 的原理?
作用
ReentrantReadWriteLock
是一个可重入的读写锁。ReentrantReadWriteLock
维护了一对读写锁,将读写锁分开,有利于提高并发效率。
原理
ReentrantReadWriteLock
本质上也是基于 AQS 实现。有三个核心字段:
sync
- 内部类 ReentrantReadWriteLock.Sync
对象。与 ReentrantLock
类似,它有两个子类:ReentrantReadWriteLock.FairSync
和 ReentrantReadWriteLock.NonfairSync
,分别表示公平锁和非公平锁的实现。readerLock
- 内部类 ReentrantReadWriteLock.ReadLock
对象,这是一把读锁。writerLock
- 内部类 ReentrantReadWriteLock.WriteLock
对象,这是一把写锁。Condition 有什么用?
使用 Lock 的线程,彼此如何通信?
作用
可以理解为,什么样的锁配什么样的钥匙。
内置锁(synchronized
)配合内置条件队列(wait
、notify
、notifyAll
),显式锁(Lock
)配合显式条件队列(Condition
)。
如何避免死锁?
👉 参考阅读:Java 原子类
为什么要用原子类?
用过哪些原子类?
作用
常规的锁(Lock
、sychronized
)由于是阻塞式的,势必影响并发吞吐量。
volatile
号称轻量级的锁,但不能保证原子性。
为了兼顾原子性和锁的性能问题,所以引入了原子类。
类型
原子变量类可以分为 4 组:
AtomicBoolean
- 布尔类型原子类AtomicInteger
- 整型原子类AtomicLong
- 长整型原子类AtomicReference
- 引用类型原子类AtomicMarkableReference
- 带有标记位的引用类型原子类AtomicStampedReference
- 带有版本号的引用类型原子类AtomicIntegerArray
- 整形数组原子类AtomicLongArray
- 长整型数组原子类AtomicReferenceArray
- 引用类型数组原子类AtomicIntegerFieldUpdater
- 整型字段的原子更新器。AtomicLongFieldUpdater
- 长整型字段的原子更新器。AtomicReferenceFieldUpdater
- 原子更新引用类型里的字段。👉 参考阅读:Java 并发工具类
CountDownLatch 作用?
CountDownLatch 原理?
作用
字面意思为 递减计数锁。用于控制一个或者多个线程等待多个线程。
CountDownLatch
维护一个计数器 count,表示需要等待的事件数量。countDown
方法递减计数器,表示有一个事件已经发生。调用 await
方法的线程会一直阻塞直到计数器为零,或者等待中的线程中断,或者等待超时。
原理
CountDownLatch
是基于 AQS(AbstractQueuedSynchronizer
) 实现的。
CyclicBarrier 有什么用?
CyclicBarrier 的原理是什么?
CyclicBarrier 和 CountDownLatch 有什么区别?
作用
字面意思是 循环栅栏。CyclicBarrier
可以让一组线程等待至某个状态(遵循字面意思,不妨称这个状态为栅栏)之后再全部同时执行。之所以叫循环栅栏是因为:当所有等待线程都被释放以后,CyclicBarrier
可以被重用。
CyclicBarrier
维护一个计数器 count。每次执行 await
方法之后,count 加 1,直到计数器的值和设置的值相等,等待的所有线程才会继续执行。
原理
CyclicBarrier
是基于 ReentrantLock
和 Condition
实现的。
区别
CyclicBarrier
和 CountDownLatch
都可以用来让一组线程等待其它线程。与 CyclicBarrier
不同的是,CountdownLatch
不能重用。
Semaphore 作用?
作用
字面意思为 信号量。Semaphore
用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。
Semaphore
管理着一组虚拟的许可(permit),permit 的初始数量可通过构造方法来指定。每次执行 acquire
方法可以获取一个 permit,如果没有就等待;而 release
方法可以释放一个 permit。
Semaphore
可以用于实现资源池,如数据库连接池。Semaphore
可以用于将任何一种容器变成有界阻塞容器。👉 参考阅读:Java 线程池
ThreadPoolExecutor
有哪些参数,各自有什么用?
ThreadPoolExecutor
工作原理?
原理
参数
java.uitl.concurrent.ThreadPoolExecutor
类是 Executor
框架中最核心的一个类。
ThreadPoolExecutor 有四个构造方法,前三个都是基于第四个实现。第四个构造方法定义如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
参数说明:
corePoolSize
- 核心线程数量。当有新任务通过 execute
方法提交时 ,线程池会执行以下判断:
corePoolSize
,则创建新线程来处理任务,即使线程池中的其他线程是空闲的。corePoolSize
且小于 maximumPoolSize
,则只有当 workQueue
满时才创建新的线程去处理任务;corePoolSize
和 maximumPoolSize
相同,则创建的线程池的大小是固定的。这时如果有新任务提交,若 workQueue
未满,则将请求放入 workQueue
中,等待有空闲的线程去从 workQueue
中取任务并处理;maximumPoolSize
,这时如果 workQueue
已经满了,则使用 handler
所指定的策略来处理任务;corePoolSize
=> workQueue
=> maximumPoolSize
。maximumPoolSize
- 最大线程数量。
keepAliveTime
:线程保持活动的时间。
corePoolSize
的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime
。unit
- keepAliveTime
的时间单位。有 7 种取值。可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。workQueue
- 等待执行的任务队列。用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。
ArrayBlockingQueue
- 有界阻塞队列。
LinkedBlockingQueue
- 无界阻塞队列。
Integer.MAX_VALUE
。ArrayBlockingQueue
。LinkedBlockingQueue
意味着: maximumPoolSize
将不起作用,线程池能创建的最大线程数为 corePoolSize
,因为任务等待队列是无界队列。Executors.newFixedThreadPool
使用了这个队列。SynchronousQueue
- 不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
LinkedBlockingQueue
。Executors.newCachedThreadPool
使用了这个队列。PriorityBlockingQueue
- 具有优先级的无界阻塞队列。threadFactory
- 线程工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字。handler
- 饱和策略。它是 RejectedExecutionHandler
类型的变量。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。线程池支持以下策略:
AbortPolicy
- 丢弃任务并抛出异常。这也是默认策略。DiscardPolicy
- 丢弃任务,但不抛出异常。DiscardOldestPolicy
- 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。CallerRunsPolicy
- 只用调用者所在的线程来运行任务。RejectedExecutionHandler
接口来定制处理策略。如记录日志或持久化不能处理的任务。Executors 提供了哪些内置的线程池?
这些线程池各自有什么特性?适合用于什么场景?
Executors 为 Executor,ExecutorService,ScheduledExecutorService,ThreadFactory 和 Callable
类提供了一些工具方法。
(1)newSingleThreadExecutor
创建一个单线程的线程池。
只会创建唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它 。
单工作线程最大的特点是:可保证顺序地执行各个任务。
(2)newFixedThreadPool
创建一个固定大小的线程池。
每次提交一个任务就会新创建一个工作线程,如果工作线程数量达到线程池最大线程数,则将提交的任务存入到阻塞队列中。
FixedThreadPool
是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
(3)newCachedThreadPool
创建一个可缓存的线程池。
CachedThreadPool
时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。(4)newScheduleThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。