02 Java 高级01(集合和多线程)
2.1 常见的数据结构有哪些?(了解)
a、逻辑结构
集合(元素无关联)
线性表(一对一关系:线性表、栈、队列)
英杰昨天吃烧烤、茄子、韭菜、羊肉串
栈:羊肉串、韭菜、茄子
队列:先进先出(用在排队)
树形结构(一对多关系)
图形结构(多对多关系)
b、物理结构:`
顺序结构:查询修改快;删除插入效率低
链式结构:查询修改慢;删除插入效率高
索引结构:耗费额外空间;查找效率高
散列结构:查找效率高;存取效率低
2.2 Java集合体系有什么?(必会)
集合类存放于 Java.util 包中,主要有 3 种:set(集)、list(列表包含 Queue) 和 map(映射)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z8OoGXdj-1676301614697)(img\wps2BD7.tmp.png)]
2.3 List实现类和特点
a、ArrayList(数组)(线程不安全)
想实现ArrayList线程安全、请使用:
1、Collections.synchronizedList(new ArrayList<>());
b、Vector(数组实现、线程安全、已被ArrayList替代)
c、LinkedList(链表)
d、Stack、Queue(栈和队列、数组实现)
2.4 Set实现类和特点
a、HashSet:由HashMap实现、无法排序
b、TreeSet:由TreeMap实现、特点参考TreeSet
2.5 HashMap的数据结构(必问)
a、HashMap:
1、以哈希表数据结构实现
2、线程不安全
3、默认数组+链表实现(哈希冲突用拉链法来解决)
4、在Jdk1.8,数据结构变化为数组+链表+红黑树的存储方式,当链表长度超过阈值(8)时并且数组长度大于64,将链表转换为红黑树
5、扩容因子是0.75、初始容量是16、意思就是存储数据量到13时、整体容量会变成32、以此类推
6、HashMap使用的是拉链法、哈希碰撞的解决方法(开放地址法、再哈希法、链地址法(拉链法)、建立一个公共溢出区)
7、HashMap 扩容后,原来的元素,要么在原位置,要么在原位置+原数组长度 那个位置上。
想实现线程安全使用:1、ConcurrentHashMap、2、Collections.synchronizedMap()
b、HashTable:和HashMap一样、不允许null为key、线程安全、已被HashMap替代
b、TreeMap:底层数据结构是红黑树、key必须实现Comparable接口
c、为什么使用红黑树:因为红黑树是性能和查找效率相对平衡的树;为什么红黑树性能和查找效率相对平衡:由红黑树的特点决定的
2.6 List、Set 和 Map的区别
a、List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;
b、List 中存储的数据是有顺序,并且允许重复;
c、Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的,
d、Set 中存储的数据是无序的,且不允许有重复
2.8 进程和线程区别(必会)
进程:是一块独立的内存空间、一个应用程序就是一个进程、一个进程可以有多个线程服务
线程:CPU调度资源的最小单位
地址空间 | 资源 | 并发性 | 切换 | |
---|---|---|---|---|
进程 | 进程之间是独立的地址空间 | 进程之间的资源是独立的,能很好的进行资源管理和保护 | 可并发执行 | 进程切换时,消耗的资源大,效率低。 |
线程 | 线程共享本进程的地址空间 | 线程共享本进程的资源如内存、I/O、cpu 等,不利于资源的管理和保护 | 可并发执行 | 线程切换时,消耗的资源小,效率高。 |
2.9 什么是并发?什么是并行?(必会)
a、并发:一个CPU、多个线程抢夺一个资源、指应用能够交替执行不同的任务。
b、并行:多个CPU、多个线程抢夺一个资源、指应用能够同时执行不同的任务。
c、 两者区别:并发是交替执行,并行是同时执行。
2.10多线程优缺点?
a、优点:当一个线程进入等待状态或者阻塞时,CPU 可以先去执行其他线程,提高 CPU 的利用。
b、缺点:
1. 上下文切换:频繁的上下文切换会影响多线程的执行速度。
2. 不恰当的编程有可能会出现死锁
3. 资源限制:在进行并发编程时,程序的执行速度受限于计算机的硬件或软件资源。在并发编程中,程序执行变快的原因是将程序中串行执行的部分变成并发执行,如果因为资源限制,并发执行的部分仍在串行执行,程序执行将会变得更慢,因为程序并发需要上下文切换和资源调度。
2.11 Java 中常见线程方式(必问)
a、 继承 Thread 类
class MyThread01 extends Thread{
@Override
public void run() {
System.out.println(2);
}
}
b、实现 Runnable 接口
class MyRun implements Runnable{
public void run() {
System.out.println(2);
}
}
c、 实现 Callable 接口:带返回值
public class Test03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
//创建线程并启动
new Thread(futureTask).start();
//等我执行完毕返回结果
int result = futureTask.get();
System.out.println(result);
}
}
class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
return 100;
}
}
d、线程池创建线程
2.12 线程常用方法
a、currentThread():返回正在执行的线程对象
b、**sleep()**:使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。
c、**join()**:等待该线程执行结束
d、**yield()**:放弃被CPU调度权,让CPU重新调用线程(自己和其他线程)
e、**wait()**:当前线程放弃监视器并进入睡眠状态,直到其他进入同一个监视器的线程调用 notify 为止。
f、**notify()**:唤醒同一监听器中调用 wait 的某一个线程。
2.13 run 和 start 区别?
线程是通过 Thread 对象所对应的方法 run() 来完成其操作的,而线程的启动是通过 start() 方法执行的。
run() 方法可以重复调用,start() 方法只能调用一次。
run()只是在当前线程中执行任务,而start才是真正生成thread,并放在cpu中调度。
start方法会new出一个新的线程进行启动,run方法只是一个普通方法
线程的生命周期(必问)
初始----启动----运行—(死亡)阻塞-----(运行、死亡)
什么时候会阻塞:sleep、join
2.14 sleep 和 wait 区别?
a、sleep 是线程中的方法,但是 wait 是 Object 中的方法。
b、sleep 方法不会释放 lock,但是 wait 会释放,而且会加入到等待队列中。
c、sleep 方法不依赖于同步器 synchronized,但是 wait 需要依赖 synchronized 关键字。
d、sleep 不需要被唤醒(休眠之后退出阻塞),但是 wait 需要(不指定时间需要被别人中断)。
2.15 synchronized 关键字的使用方式?
a、synchronized可以锁住方法、锁住的方法就是同步方法
b、synchronized可以锁住对象或类、锁住的对象或类执行的代码就是 同步代码块
e、同步锁、资源默认状态是1、一旦被锁住这个资源就变成0
2.16 线程的 sleep() 方法和 yield() 方法有什么不同?
a、sleep() 方法会使得当前线程暂停指定的时间,没有消耗 CPU 时间片。
b、sleep()使得线程进入到阻塞状态,yield() 只是对 CPU 进行提示,如果 CPU 没有忽略这个提
示,会使得线程上下文的切换,进入到就绪状态。
c、sleep() 一定会完成给定的休眠时间,yield() 不一定能完成。
d、sleep() 需要抛出 InterruptedException,而 yield() 方法无需抛出异常。
2.17 什么是死锁、如何避免(必问)
死锁的四个必要条件
1、互斥条件:一个资源每次只能被一个进程使用;
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
3、不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,而该资源又被其他线程锁定,从而导致每一个线程都得等其它线程释放其锁定的资源,造成了所有线程都无法正常结束。
死锁:多个线程多把锁、1个线程拿着A锁等B锁、另一个线程拿着B锁等A锁
静态死锁:两把锁、两个线程、线程A拿着锁1等锁2、线程B拿着锁2等锁1
避免静态死锁:1、不要嵌套锁;2、如果有嵌套锁,锁的顺序请保持一致
2.18 什么是 GC、作用是什么(必问)
GC:垃圾回收机制、清理对象,整理内存
2.19 GC回收的区域在哪里?(必问)
JVM 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭, 栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理。因此, 我们的内存垃圾回收主要集中于 Java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。
长时间没有被引用的对象就会被GC回收掉
2.20 GC 的操作对象是什么?
需要进行回收的对象就是已经没有存活的对象,判断一个对象是否存活常用的有两种办法:引用计数和可达分析。
2.21 GC 的时机是什么?(必问)
(1) 程序调用 System.gc 时可以触发。
(2) 系统自身来决定 GC 触发的时机(根据 Eden 区和 From Space 区的内存大小来决定。当内存大小不足时,则会启动 GC 线程并停止应用线程)。
2.22 GC 常用算法
GC 常用算法有:标记-清除算法,标记-压缩算法,复制算法,分代收集算法。目前主流的 JVM(HotSpot)采用的是分代收集算法。
a、标记-清除算法
为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作。
b、记-压缩算法(标记-整理
标记-压缩法是标记-清除法的一个改进版。同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间, 然后把剩下的所有对象全部清除。这样就达到了标记-整理的目的。
c、复制算法
该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。
d、分代收集算法
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代(Young)和老年代(Tenure)。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高, 没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。
2.23 JVM 调优的工具有哪些?(了解)
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
-
jconsole:用于对 JVM 中的内存、线程和类等进行监控;
-
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。
2.24 常用的 JVM调优的参数都有哪些?(必会)
XX 比 X 的稳定性更差,并且版本更新不会进行通知和说明。
a、-Xms:s 为 strating,表示堆内存起始大小。
b、-Xmx:x 为 max,表示最大的堆内存(一般来说-Xms 和-Xmx 的设置为相同大小,因为当 heap 自动扩容时,会发生内存抖动,影响程序的稳定性)。
c、-Xmn:n 为 new,表示新生代大小(-Xss:规定了每个线程虚拟机栈(堆栈)的大小)。
d、-XX:SurvivorRator=8 表示堆内存中新生代、老年代和永久代的比为
e、-XX:PretenureSizeThreshold=3145728 表示当创建(new)的对象大于3M 的时候直接进入。
f、-XX:MaxTenuringThreshold=15 表示当对象的存活的年龄(minor gc 一次加 1)大于多少时,进入老年代。
g、-XX:-DisableExplicirGC 表示是否(+表示是,-表示否)打开 GC 日志。
2.25 JVM堆内存划分(必问)
1、新生代:比如我们在方法中去new一个对象,那这方法调用完毕后,对象就会被回收,这就是一个典型的新生代对象
2、老年代:在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。而且大对象直接进入老年代。
3、永久代【元空间】: 即方法区。
2.26 类的加载过程(必问)
加载----连接(验证、准备、解析)----初始化
2.27 JVM内存结构(必问)
程序计数器、虚拟机栈、本地方法栈、堆内存、方法区
JVM内存结构:
老的:程序计数器、虚拟机栈、本地方法栈、堆内存、方法区
新的:虚拟机栈、本地方法栈、程序计数器、堆、元空间
JVM堆内存结构:
1、新生代:比如我们在方法中去new一个对象,那这方法调用完毕后,对象就会被回收,这就是一个典型的新生代对象
2、老年代:在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。而且大对象直接进入老年代。
3、永久代【元空间】: 即方法区。
Java内存结构:
在Java中、每个线程有自己的独立内存空间、子线程操作主内存的变量时、会把主内存的变量复制到子内存空间
本地方法栈、堆内存、方法区
新的:虚拟机栈、本地方法栈、程序计数器、堆、元空间
JVM堆内存结构:
1、新生代:比如我们在方法中去new一个对象,那这方法调用完毕后,对象就会被回收,这就是一个典型的新生代对象
2、老年代:在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。而且大对象直接进入老年代。
3、永久代【元空间】: 即方法区。
Java内存结构:
在Java中、每个线程有自己的独立内存空间、子线程操作主内存的变量时、会把主内存的变量复制到子内存空间