-
什么是进程?什么是线程?有什么区别?
- 进程是系统资源分配的基本单位,拥有独立的地址空间。线程是 CPU 调度和分派的基本单位,是比进程更小的独立执行的单位,共享所在进程的内存空间等资源。一个进程可以包含多个线程。
-
说说线程的不同状态?
- NEW:新建状态,线程被创建但尚未启动。
- RUNNABLE:就绪状态,等待 CPU 调度执行。
- BLOCKED:阻塞状态,如等待 I/O 操作、获取锁等。
- WAITING:等待状态,如调用 Object.wait() 方法后进入该状态,等待其他线程通知或中断。
- TIMED_WAITING:超时等待状态,在指定时间内等待其他线程执行特定操作,如调用带有超时参数的 Object.wait(long timeout) 方法。
- TERMINATED:终止状态,线程执行完毕。
-
并发与并行、串行的区别?
- 并发是指多个任务同时进行,但不一定同时完成;并行是指多个任务同时进行且同时完成;串行则是指任务依次顺序执行。
-
synchronized 关键字的作用?
- 实现对共享资源的互斥访问,保证同一时刻只有一个线程可以访问被 synchronized 修饰的方法或代码块,避免数据不一致等问题。它是基于对象头和 Monitor 机制实现的。
-
synchronized 关键字的原理是什么?
- 基于 Java 对象头和 Monitor 机制。每个对象都有对象头信息,其中包含锁状态标记位等信息。当线程访问 synchronized 方法或代码块时,会根据对象头信息判断是否需要获取锁,若需要则通过 CAS 等方式修改锁状态标记位,将锁与当前线程关联,执行完同步代码后释放锁。
-
synchronized 关键字是可重入锁吗?为什么?
- 是可重入锁。因为同一个线程可以多次获取同一个对象的锁而不会被阻塞,这是为了避免自己锁死自己,提高程序的灵活性和效率。
-
多线程编程中,什么是上下文切换?
- 多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
-
创建线程的方式有几种?
- 继承 Thread 类:定义一个继承自 Thread 类的子类,重写 run() 方法,在 run() 方法中编写线程要执行的代码,然后创建该子类的实例并调用 start() 方法启动线程。
- 实现 Runnable 接口:定义一个实现 Runnable 接口的类,实现 run() 方法,在 run() 方法中编写线程要执行的代码,然后创建该类的实例,将其作为参数传递给 Thread 类的构造函数来创建线程对象,最后调用线程对象的 start() 方法启动线程。
- 使用 Callable 和 Future:定义一个实现 Callable 接口的类,实现 call() 方法,在 call() 方法中编写线程要执行的代码并返回一个结果,然后创建该类的实例,将其作为参数传递给 Executors 类的静态方法(如 Executors.submit(Callable task))来提交任务并获得一个 Future 对象,通过 Future 对象可以获取任务的执行结果。
-
线程池的核心构造参数有哪些?
- corePoolSize:核心线程数,即线程池中始终保持活跃的线程数量。
- maximumPoolSize:最大线程数,即线程池中允许的最大线程数量。
- keepAliveTime:非核心线程闲置的最大时间,超过这个时间非核心线程将被终止。
- unit:时间单位,用于指定 keepAliveTime 的单位。
-
什么是死锁?如何避免和解决死锁?
- 死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果没有外力作用,它们都将无法推进下去。
- 避免死锁的方法包括:避免一个线程同时获得多个锁;避免一个线程在持有一个锁的同时申请其他锁;给锁的获取设置一定的顺序等。
- 解决死锁的方法有:使用 tryLock() 方法尝试获取锁,若获取失败则不再继续等待;使用 LockSupport 类的 park() 和 unpark() 方法来控制线程的挂起和恢复执行;采用死锁检测算法,定期检查系统中是否存在死锁,若存在则采取相应措施解除死锁。
-
并发编程三要素是什么?
- 原子性:指一个操作或者一组操作要么全部执行成功,要么全部执行失败,不会被其他线程干扰而导致中间状态的出现。
- 可见性:指一个线程对共享变量的修改能够及时被其他线程看到,以保证数据的一致性。
- 有序性:指程序执行的结果必须按照代码的顺序执行,不能出现乱序的情况。
-
什么是并发容器?常见的并发容器有哪些?
- 并发容器是指专门设计用于多线程环境下的线程安全的集合类。常见的并发容器有 ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue 等。
-
Java 中的并发工具类有哪些?
- CountDownLatch:用于让一个或多个线程等待其他线程完成操作后再继续执行,常用于实现线程间的同步。
- CyclicBarrier:类似于 CountDownLatch,但它可以重复使用,允许一组线程互相等待到达某个公共屏障点后再继续执行。
- Semaphore:信号量,用于控制对共享资源的访问数量,可以实现资源的限流。
- Exchanger:用于在两个线程之间交换数据。
- ReadWriteLock:读写锁,允许多个读线程同时访问资源,但写线程的访问是独占的。
-
什么是线程的中断机制?如何使用中断机制来控制线程的执行?
- 线程的中断机制是指在线程执行过程中,可以通过其他线程发送中断请求来通知该线程停止执行或做出相应的处理。在 Java 中,可以通过调用线程的 interrupt() 方法来发送中断请求。线程在执行过程中可以通过响应中断来处理中断请求,例如通过检查线程的中断状态(使用 Thread.interrupted() 方法)来决定是否退出循环或执行特定的逻辑。
-
什么是线程优先级?如何设置和获取线程优先级?
- 线程优先级是指线程在竞争 CPU 资源时的优先程度。在 Java 中,可以使用 Thread 类的 setPriority(int newPriority) 方法来设置线程的优先级,使用 getPriority() 方法来获取线程的优先级。线程优先级分为 10 个级别,从 Thread.MIN_PRIORITY(1)到 Thread.MAX_PRIORITY(10),默认优先级为 Thread.NORM_PRIORITY(5)。
-
什么是守护线程?守护线程与用户线程有什么区别?
- 守护线程是为其他线程提供服务的线程,通常在后台默默地执行一些系统任务,比如垃圾回收线程等。守护线程的特点是,当所有的用户线程都结束时,守护线程也会自动结束,而不管守护线程本身的执行状态如何。用户线程是由用户程序主动创建和启动的线程,用于执行具体的业务逻辑。
-
什么是线程安全问题?如何保证线程安全?
- 线程安全问题是指在多线程环境下,由于多个线程同时访问和修改共享数据而导致的数据不一致、竞态条件等问题。为了保证线程安全,可以采取以下措施:使用同步机制(如 synchronized 关键字、Lock 接口等)来控制对共享资源的访问;使用线程安全的类(如 ConcurrentHashMap、AtomicInteger 等);避免共享可变数据,尽量使用不可变对象;对临界区代码进行合理的设计和优化,减少线程间的竞争。
-
什么是乐观锁和悲观锁?它们有什么区别?
- 乐观锁是一种假设操作总是能够成功的锁机制,它不会阻塞其他线程对共享资源的访问,而是在更新数据时通过版本号或其他验证方式来检查是否有其他线程已经修改了数据,如果有则放弃本次操作并重试。悲观锁则是假设操作可能会失败,因此在访问共享资源时直接阻塞其他线程,直到当前线程完成操作为止。
-
什么是类加载器?类加载器的工作原理是什么?
- 类加载器负责将类的字节码文件加载到 JVM 中,并将其转换为 Class 对象,以便在程序中创建该类的实例和调用其方法。类加载器的工作原理主要包括以下几个步骤:首先,根据类的全限定名找到对应的字节码文件;然后,将字节码文件读取到内存中;接着,对字节码进行验证、准备和解析等操作,生成 Class 对象;最后,将 Class 对象存储到方法区中,以便后续使用。
-
什么是双亲委派模型?为什么要使用双亲委派模型?
- 双亲委派模型是指当一个类加载器收到一个类加载请求时,它首先会将请求委托给其父类加载器去处理,直到最顶层的类加载器(通常是引导类加载器)无法处理该请求时,才会由当前的类加载器自己去加载该类。使用双亲委派模型的好处是可以保证类的唯一性和安全性,防止不同的类加载器加载相同的类导致冲突和安全问题。
-
什么是垃圾回收(GC)?垃圾回收的机制是什么?
- 垃圾回收(GC)是 Java 虚拟机自动管理内存的一种机制,它会定期扫描内存中不再使用的对象并将其释放,以回收内存空间,防止内存泄漏。垃圾回收的机制主要包括标记 - 清除、复制、标记 - 整理等算法。标记 - 清除算法会先标记出所有可达的对象,然后将未被标记的对象视为垃圾进行回收;复制算法则会将内存分为两个区域,将存活的对象复制到另一个区域中,然后清空原区域;标记 - 整理算法是在标记 - 清除算法的基础上,对存活的对象进行整理,使其在内存中连续存储。
-
Java 中的内存模型是怎样的?
- Java 内存模型(JMM)将内存分为堆内存、方法区、栈内存和本地方法栈等几个区域。堆内存用于存储对象实例;方法区用于存储类的元数据信息、静态变量和常量池等;栈内存用于存储方法的局部变量和参数;本地方法栈则用于存储 native 方法的局部变量和参数。
-
什么是逃逸分析?逃逸分析的作用是什么?
- 逃逸分析是指编译器对变量的作用域和使用情况进行分析,以确定该变量是否会被其他线程访问或是否可以被优化为栈上分配。逃逸分析的作用主要包括提高性能和优化内存使用。如果一个变量不会逃逸出方法或线程,那么编译器可以将其优化为栈上分配,从而减少了堆内存的使用和垃圾回收的压力。
-
什么是CAS?CAS 有什么作用?
- CAS(Compare And Swap)是一种无锁的同步机制,它通过比较和交换操作来实现对共享资源的原子性更新。CAS 的作用是避免了使用传统的互斥锁带来的性能开销和线程阻塞问题,提高了并发性能。在 Java 中,CAS 操作通常用于实现原子变量的更新和同步容器的实现。
-
什么是 AQS?AQS 的作用是什么?
- AQS(AbstractQueuedSynchronizer)是一个用于构建同步组件的框架,它提供了队列、锁、条件变量等基础功能,许多同步类(如 ReentrantLock、CountDownLatch、Semaphore 等)都是基于 AQS 实现的。AQS 的作用是简化了同步组件的开发难度,提供了一种统一的同步解决方案。
-
什么是并发编程三原则?
- SA(Single Responsibility):单一职责原则,即一个类或方法应该只有一个引起变化的原因。
- OCP(Open/Closed):开闭原则,即软件实体应该对扩展开放,对修改关闭。
- LSP(Liskov Substitution):里氏替换原则,即子类对象能够替换父类对象而不影响程序的正确性。
-
Java 中的并发编程模型有哪些?
- Java 中的并发编程模型主要包括:基于锁的编程模型(如使用 synchronized 关键字和 Lock 接口)、基于并发工具类的编程模型(如使用 CountDownLatch、CyclicBarrier、Semaphore 等)、基于并发容器的编程模型(如使用 ConcurrentHashMap、CopyOnWriteArrayList 等)以及基于 Actor 模型的编程模型(Java 本身没有直接支持 Actor 模型,但可以通过第三方库如 Akka 来实现)。
-
什么是线程的并发与并行?
- 并发是指多个任务同时进行,但不一定同时完成;并行是指多个任务同时进行且同时完成。在单核 CPU 上,真正的并行是不可能的,只能通过并发来模拟并行的效果;而在多核 CPU 上,可以实现真正的并行执行。
-
Java 中的锁机制有哪些?它们之间有什么区别?
- Java 中的锁机制主要包括:公平锁、类锁、对象锁和可重入锁、死锁、类锁、对象锁和可重入锁、偏向锁、轻量级锁和重量级锁等。公平锁是指按照线程请求锁的顺序来分配锁;类锁是锁定当前类的所有对象实例;对象锁是锁定特定的对象实例;可重入锁是指同一个线程可以多次获取同一个锁而不会被阻塞;死锁是指两个或两个以上的线程因争夺资源而造成的一种互相等待的现象;类锁、对象锁和可重入锁是三种基本的锁类型;偏向锁是为了减少无竞争情况下的同步开销而引入的一种优化机制;轻量级锁是在对象头中记录锁的信息,是一种开销较小的锁实现;重量级锁则是在竞争激烈的情况下使用的较为重量级的锁实现。
-
什么是线程的生命周期?线程的不同状态之间是如何转换的?
- 线程的生命周期包括创建、就绪、运行、阻塞、死亡等状态。创建状态是指线程被创建但尚未启动;就绪状态是指线程已经具备运行条件,等待 CPU 调度执行;运行状态是指线程正在 CPU 上执行;阻塞状态是指线程因为某种原因(如等待 I/O 操作、获取锁等)而不能继续执行;死亡状态是指线程执行完毕或因异常退出。