Java基础
JDK和JRE的区别?
JDK全称Java Development Kit,包含Java运行环境(JRE)和开发环境,面向Java开发者,提供了一系列开发工具,比如 javac,需要配置环境变量;
JRE全称Java Runtime Enviroment,是Java运行环境,面向Java使用者,不需要配置环境变量;equals()
和==
的区别?==
比较的是引用,即在内存空间中的存储位置是否一致;equals()
是Object
类提供的方法,可以由子类重写,如何比较由开发者决定,一般比较的是内容;1
2
3public boolean equals(Object obj){
return (this == obj);
}Object
类中的equals()
方法的默认实现和==
等价,Java 中很多类都对equals()
方法进行了重写,比如String
;两个对象的
hashCode()
相同,则equals()
也一定为true
,对吗?理论上要求:
equals()
返回true
,hashCode()
返回值相等;hashcode()
返回值相等,equals()
不一定返回true
;实际上:
equals()
返回值相等,hashCode()
返回值不一定相等;equals()
重写后,还要重写hashcode()
,hashCode()
能提高哈希表的性能;final
finally
finalize
的区别?final
:修饰类:该类不能被继承,
final
类中所有的成员方法都会隐式的定义为final
方法;修饰方法:该方法不能被重写;
修饰变量:常量,只能赋值一次,不能更改;
finally
:1
2
3
4
5
6
7try {
...
}catch(Exception e){
...
}finally {
...
}finally
经常被用作释放资源;finalize()
:这个方法在gc启动,该对象被回收的时候被调用;
Java基本数据类型?
byte
short
int
long
double
float
char
boolean
;Double
和Float
包装类没有常量池;String
StringBuffer
StringBuilder
的区别?String
:字符串常量,线程安全;StringBuffer
:字符串变量,线程安全;StringBuilder
:字符串变量,线程不安全;抽象类和接口的区别?
- 默认方法实现:抽象类可以有默认方法,接口不可以(Java 8 可以)
- 实现:继承抽象类使用extends,实现接口使用implements
- 构造方法:抽象类有构造方法,接口没有构造方法
- 访问修饰符:抽象类有public、protected、default、private,接口只有public
- main方法:抽象类有main方法,接口没有main方法
- 添加新方法:抽象类添加新方法不需要改变子类方法,接口需要改变子类
switch
接受数据类型byte
short
int
char
String
enum
不支持
boolean
!空接口的作用
标志着Java类实现了某个特性或功能Java 泛型
类型参数化,将类型转换的类型检查从运行时提前到编译时。省去了强制类型转换,提高安全性。自动装箱和自动拆箱
- 自动装箱:静态方法
Integer.valueOf()
- 自动拆箱:实例方法
i.intValue()
- 自动装箱:静态方法
异常
- 可检查异常:编译期间能检查到的异常,需要在代码中进行捕获或抛出异常,比如
IOException
- 运行时异常:只有在运行期才能知道的异常,比如空指针异常,数组越界
- Error:会导致程序处于不正常状态,不需要捕获,比如
OutOfMemoryError
- 可检查异常:编译期间能检查到的异常,需要在代码中进行捕获或抛出异常,比如
foreach 与 for
- for:遍历数组结构的数据,采用 for 更优
- foreach:遍历链表结构的数据,采用 foreach 更优
静态分派和动态分派
Java容器
ArrayList
- 默认大小:10
- 扩容:原容量的1.5倍,$oldCapacity + (oldCapacity >> 1)$,使用
Arrays.copyOf()
复制数组 - 删除元素:使用
System.copyOf()
复制数组 modCount
:记录ArrayList
结构发生变化的次数。ArrayList
使用size
维护自身状态,Iterator
使用cursor
维护自身状态,不同步
Vector
默认大小:10
扩容:原容量的2倍
CopyOnWriteArrayList
读写分离,写时复制,在写时允许读操作,适用于读多写少的场景;读不加锁,写加锁;不适合内存敏感以及对实时性要求很高的场景;
LinkedList
- 默认大小:0
ArrayList
和LinkedList
的比较ArrayList
使用动态数组,LinkedList
使用双向链表ArrayList
支持随机访问,LinkedList
不支持ArrayList
插入删除耗时长,LinkedList
能快速插入删除
HasMap
- 默认大小:16
- 扩容:原容量的2倍
- 从 JDK 1.8 开始,一个桶存储的链表长度大于 8 时会将链表转换为红黑树
- 允许
key = null
,放在第0个桶内 - 插入元素使用头插法
- 遍历时顺序是随机的
- 非线程安全
- hash
- HashMap为什么线程不安全(hash碰撞与扩容导致)
- HashMap的死循环
fail-fast
fail-fast 机制是java集合中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。HashTable
- 默认大小:11
- 扩容:
newsize = olesize*2+1
- 线程安全
- 不允许
key = null
- 迭代器是强一致性
- HashMap与Hashtable的区别是面试中经常遇到的一个问题
LinkedHashMap
- 保存了记录的插入顺序,遍历时是有序的
TreeMap
- 能够根据键排序,默认按照键的升序排序,遍历时是排好序的
ConcurrentHashMap
- 分段锁,初始并发 16
- 扩容:segment 内扩容
- 迭代器是弱一致性:如果已经遍历完的数据发生了变化,不会抛异常;未遍历到的数据发生了遍历,会遍历到新的数据
Java 多线程
线程池使用不当会造成内存溢出
Java Concurrency代码实例之一执行者与线程池
多线程面试题(一)
多线程面试题(二)
Java 提供了 JUC 包,包括:
执行者和线程池
- 执行者:
Executor
根接口、ExecutorService
接口、Executors
工具类 - 线程池:
ThreadPollExecutor
线程池、Executors.newCachedThreadPool()
线程池、Executors.newFixedThreadPool(int)
线程池、Executors.newSingleThreadPool(int)
线程池 - 异步值:
Future
接口、FutureTask
ForkJoin
:ForkJoinPool
、RecursiveAction
、RecursiveTask
阻塞队列
- LinkedBlockingQueue:链表实现,不可实现公平策略
- ArrayedBlockingQueue:数组实现,实现公平策略
- SynchronousQueue:支持公平策略
- PriorityBlockingQueue
- DelayQueue
- BlockingDeque
- ConcurrentLinkedQueue
- ConcurrentLinkedDeque
同步工具
CountdownLatch
Semaphore
CyclicBarrier
Exchange
并发集合
- ConcurrentHashMap
- ConcurrentSkipListMap
- CopyOnWriteArrayList
锁
LockSupport
:能够实现线程的阻塞和唤醒,不需要放在同步代码块中;unpark()
可以优先于park()
调用AQS
:一个状态值state
、阻塞和唤醒线程的能力LockSupport
、阻塞队列RentrantLock
Condition
ReentrantWriteReadLock
原子变量
- AtomicInteger
- AtomicLong
- AtomicBoolean
- AtomicReference
- AtomicStampedReference
- AtomicMarkableReference
synchronized volatile
synchronized:保证原子性和可见性,不会限制指令重排
volatile:保证可见性和有序性,无法保证原子性。Volatile 可用于提供线程安全,但具有使用条件:多个变量之间或某个变量的当前值与修改后的值没有约束。Volatile 用于多线程环境下的单次操作(单次读或单次写)。Volatile 的写操作不能依赖于当前值,读操作不能依赖于其他变量
- 状态标记
- 一次性安全发布
- volatile bean 模式
- 开销较低的“读写锁”
双重检查单例模式
1 | public class Singleton { |
- synchronized:保证执行原子性
- volatile:保证有序性
如果不加 volatile 无法保证有序性,instance = new Singleton()
可能出现先创建引用再创建对象的情况,此时会出现一个问题:instance 不为 null ,但对象并没有创建万;使用 volatile ,可以保证有序性,保证先创建对象再进行引用赋值
Java 线程状态转换
- 创建
- 就绪:
Thread.start()
会创建线程,并进入就绪状态等待运行 - 运行
- 阻塞
- 同步阻塞:
synchronized
- 限期等待:
Thread.sleep()
- 无限期等待:
Obiect.wait()
- 同步阻塞:
- 终止
wait sleep yield
wait()
:Object 实例方法,线程会释放锁,线程状态由运行状态变为无限期等待状态sleep()
:Thread 静态方法,线程不会释放锁,线程由运行状态变为限期等待状态yield()
:Thread 静态方法,线程不会释放锁,线程由运行状态变为就绪状态
多线程优点
- 提高应用程序并发度和吞吐量,提高 CPU 利用率
- 能够充分利用多核 CPU 或多个 CPU
- 线程不仅有 CPU 计算时间,还有 IO 读取时间等。对于单核 CPU 来说,当一个线程等待 IO 到达而被阻塞时,可以执行其他线程,从而提高 CPU 利用率
ThreadPool
Executors.newCachedThreadPool()
1
2
3
4
5
6
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}核心线程数为 0,最大线程数为
Integer.MAX_VALUE
,空闲线程存活时间为 60s ,当线程数超过核心线程数时使用同步队列保存任务。由于使用同步队列,如果想将一个元素放入
SynchronousQueue
,必须至少一个在等待接受这个元素。所以对于缓存线程池,来一个任务,如果有空闲线程会将任务交由空闲线程处理;如果没有空闲线程,再创建一个新的线程处理。SynchronousQueue
的最大线程数是Integer.MAX_VALUE
,对于高并发任务,同时创建多个线程很有可能造成内存溢出甚至宕机。Executors.newFixedThreadPool(int)
1
2
3
4
5public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}核心线程数和最大线程数都是
nThreads
,当任务数超过线程数时使用LinkedBlockingQueue
。由于LinkedBlockingQueue
最大值是Integer.MAX_VALUE
,当高并发场景很可能队列太大导致内存溢出。Executors.newSingleThreadExecutor()
1
2
3
4
5public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}同上,单线程能保证任务有序执行。也会存在内存溢出的风险。
由于以上三种线程池在高并发任务中都存在内存溢出的风险,可以使用自己实现的线程池。选择有界队列如ArrayBlockingQueue
,并且采取适当的饱和策略,JDK 提供了 4 种饱和策略:
- 终止策略:抛异常
- 抛弃策略:抛弃任务
- 抛弃旧任务策略:抛弃任务
- 调用者策略:不抛异常也不抛弃任务,而是将任务交由主线程执行
LinkedBlockingQueue
也是有界队列,默认值为 Integer.MAX_VALUE
,也可以自己设定值
submit execute
- submit 提交 callable 方法,有返回值
- execute 提交 runnable 方法,无返回值
ThreadLocal
每个 Thread 都有 threadlocals 成员变量,其类型是ThreadLocal.ThreadLocalMap
1 | ThreadLocal.ThreadLocalMap threadLocals = null; |
当调用 ThreadLocal
类的 get()
和 set()
方法时,实际上是拿到 thread 类的 threadLocals 变量。
ThreadLocal 的应用场景:
- 数据库连接
- Session 管理
- Web 请求
- …