文章目录
- 前言
- 面经2
- 线程和进程的区别?
- 线程的上下文切换
- 线程死锁是如何产生的,如何避免
- 创建线程的几种方式?
- Java 线程同步的几种方法?
- 线程的run()和start()有什么区别?
- 如何创建线程池?
- JDK 中提供了哪些并发容器?
- volatile、synchronized的区别?
- 说说synchronized和ReentrantLock的区别?
- 什么是ThreadLocal?有哪些应用场景?
- 说下对同步器 AQS 的理解?
- AQS 的原理是什么?
- CAS呢?CAS了解多少?
- CAS 有什么问题?如何解决?
- CyclicBarrier和CountDownLatch有什么区别?
- 说下对 Fork和Join 并行计算框架的理解?
- JDK 中提供了哪些并发容器?
- Error 和 Exception 的区别?
- throw 和 throws 的区别?
- 常见的异常类有哪些?
- 说下你对 Java 内存模型的理解?
- 全部系列
前言
记录收集到的常用Java面试题(系列2)
面经2
线程和进程的区别?
进程:是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。
线程:是进程的一个实体,是 cpu 调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。
线程的上下文切换
即便是单核的处理器也会支持多线程,处理器会给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给每个线程的执行时间,一般来说时间片非常的短,所以处理器会不停地切换线程。CPU会通过时间片分配算法来循环执行任务,当前任务执行完一个时间片后会切换到下一个任务,但切换前会保存上一个任务的状态,因为下次切换回这个任务时还要加载这个任务的状态继续执行,从任务保存到在加载的过程就是一次上下文切换。
线程死锁是如何产生的,如何避免
死锁:由于两个或两个以上的线程相互竞争对方的资源,而同时不释放自己的资源,导致所有线程同时被阻塞。
死锁产生的条件:
互斥条件:一个资源在同一时刻只由一个线程占用。
请求与保持条件:一个线程在请求被占资源时发生阻塞,并对已获得的资源保持不放。
循环等待条件:发生死锁时,所有的线程会形成一个死循环,一直阻塞。
不剥夺条件:线程已获得的资源在未使用完不能被其他线程剥夺,只能由自己使用完释放资源。
避免死锁的方法主要是破坏死锁产生的条件。
破坏互斥条件:这个条件无法进行破坏,锁的作用就是使他们互斥。
破坏请求与保持条件:一次性申请所有的资源。
破坏循环等待条件:按顺序来申请资源。
破坏不剥夺条件:线程在申请不到所需资源时,主动放弃所持有的资源。
创建线程的几种方式?
继承 Thread 类创建线程;
实现 Runnable 接口创建线程;
通过 Callable 和 Future 创建线程;
通过线程池创建线程。
Java 线程同步的几种方法?
使用 Synchronized 关键字;
wait 和 notify;
使用特殊域变量 volatile 实现线程同步;
使用可重入锁实现线程同步;
使用阻塞队列实现线程同步;
使用信号量 Semaphore。
线程的run()和start()有什么区别?
线程是通过 Thread 对象所对应的方法 run() 来完成其操作的,而线程的启动是通过 start() 方法执行的。
run () 方法可以重复调用, start() 方法只能调用一次
如何创建线程池?
方式一:通过 ThreadPoolExecutor 的构造方法实现:
方式二:通过 Executor 框架的工具类 Executors 来实现:1、FixedThreadPool:2、 SingleThreadExecutor:3、CachedThreadPool
JDK 中提供了哪些并发容器?
ConcurrentHashMap:线程安全的 HashMap;
CopyOnWriteArrayList:线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector;
ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列;
BlockingQueue:这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道;
ConcurrentSkipListMap:跳表的实现。这是一个 Map,使用跳表的数据结构进行快速查找。
volatile、synchronized的区别?
volatile 主要是保证内存的可见性,即变量在寄存器中的内存是不确定的,需要从主存中读取。
synchronized 主要是解决多个线程访问资源的同步性。
volatile 作用于变量, synchronized 作用于代码块或者方法。
volatile 仅可以保证数据的可见性,不能保证数据的原子性。 synchronized 可以保证数据的可见性和原子性。
volatile 不会造成线程的阻塞, synchronized 会造成线程的阻塞。
说说synchronized和ReentrantLock的区别?
锁的实现:synchronized是Java语言的关键字,基于JVM实现。而ReentrantLock是基于JDK的API层面实现的(一般是lock()和unlock()方法配合try/finally 语句块来完成。)
性能:在JDK1.6锁优化以前,synchronized的性能比ReenTrantLock差很多。但是JDK6开始,增加了适应性自旋、锁消除等,两者性能就差不多了。
功能特点:
ReentrantLock 比 synchronized 增加了一些高级功能,如等待可中断、可实现公平锁、可实现选择性通知。
ReentrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制
ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
synchronized与wait()和notify()/notifyAll()方法结合实现等待/通知机制,ReentrantLock类借助Condition接口与newCondition()方法实现。
ReentrantLock需要手工声明来加锁和释放锁,一般跟finally配合释放锁。而synchronized不用手动释放锁。
什么是ThreadLocal?有哪些应用场景?
ThreadLocal是JDKjava.lang包下的一个类,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,并且不会和其他线程的局部变量冲突,实现了线程间的数据隔离。
ThreadLocal 的应用场景主要有以下几个方面:
保存线程上下文信息,在需要的地方可以获取
线程间数据隔离
数据库连接
说下对同步器 AQS 的理解?
AQS 是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器比如:我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask 等等皆是基于 AQS 的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
AQS 的原理是什么?
AQS 核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CAS呢?CAS了解多少?
CAS叫做CompareAndSwap,⽐较并交换,主要是通过处理器的指令来保证操作的原⼦性的。
CAS 指令包含 3 个参数:共享变量的内存地址 A、预期的值 B 和共享变量的新值 C。
只有当内存中地址 A 处的值等于 B 时,才能将内存中地址 A 处的值更新为新值 C。作为一条 CPU 指令,CAS 指令本身是能够保证原子性的 。
CAS 有什么问题?如何解决?
ABA问题 : 加版本号
循环时间过长导致开销太大 : 在Java中,很多使用自旋CAS的地方,会有一个自旋次数的限制,超过一定次数,就停止自旋。
只能保证一个共享变量的原子操作: 可以考虑改用锁来保证操作的原子性 ;可以考虑合并多个变量,将多个变量封装成一个对象,通过AtomicReference来保证原子性。
CyclicBarrier和CountDownLatch有什么区别?
CountDownLatch是一次性的,而CyclicBarrier则可以多次设置屏障,实现重复利用;
CountDownLatch中的各个子线程不可以等待其他线程,只能完成自己的任务;而CyclicBarrier中的各个线程可以等待其他线程
说下对 Fork和Join 并行计算框架的理解?
Fork/Join 并行计算框架主要解决的是分治任务。分治的核心思想是“分而治之”:将一个大的任务拆分成小的子任务的结果聚合起来从而得到最终结果。
ForkJoin并行计算框架的核心组件是ForkJoinPool。ForkJoinPool支持任务窃取机制,能够让所有的线程的工作量基本均衡,不会出现有的线程很忙,而有的线程很闲的情况,所以性能很好。
ForkJoinPool 中的任务队列采用的是双端队列,工作线程正常获取任务和“窃取任务”分别是从任务队列不同的端消费,这样能避免很多不必要的数据竞争。
JDK 中提供了哪些并发容器?
ConcurrentHashMap:线程安全的 HashMap;
CopyOnWriteArrayList:线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector;
ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列;
BlockingQueue:这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道;
ConcurrentSkipListMap:跳表的实现。这是一个 Map,使用跳表的数据结构进行快速查找。
Error 和 Exception 的区别?
Error: Error 类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理。
Exception: Exception 以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
throw 和 throws 的区别?
throws用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;throw则是指拋出的一个具体的异常类型,执行throw则一定抛出了某种异常对象
常见的异常类有哪些?
NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
SQLException:提供关于数据库访问错误或其他错误信息的异常。
IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。
IOException:当发生某种 I/O 异常时,抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。
ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
说下你对 Java 内存模型的理解?
Java 内存模型(Java Memory Model,JMM):屏蔽掉了各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致性的内存访问效果
全部系列
面经1:https://blog.csdn.net/Oliver__Twist/article/details/125459141?spm=1001.2014.3001.5501
面经2:https://blog.csdn.net/Oliver__Twist/article/details/125461522?spm=1001.2014.3001.5501
面经3:https://blog.csdn.net/Oliver__Twist/article/details/125461610?spm=1001.2014.3001.5501
面经4:https://blog.csdn.net/Oliver__Twist/article/details/125461632?spm=1001.2014.3001.5501
面经5:https://blog.csdn.net/Oliver__Twist/article/details/125461655?spm=1001.2014.3001.5501