本文概述
多线程和同步被视为Java编程中的典型章节。在游戏开发公司中, 与多线程相关的面试问题大多被问到。下面列出了一些常见的Java多线程和并发面试问题。
多线程面试问题
1)什么是多线程?
多线程是同时执行多个线程的过程。多线程用于获取多任务。它消耗更少的内存, 并提供快速有效的性能。它的主要优点是:
- 线程共享相同的地址空间。
- 线程是轻量级的。
- 进程之间的通信成本很低。
更多细节。
2)什么是线程?
线程是轻量级子进程。这是一条单独的执行路径, 因为每个线程都在不同的堆栈框架中运行。一个进程可能包含多个线程。线程共享进程资源, 但是它们仍然独立执行。
更多细节。
3)区分进程和线程?
进程和线程之间存在以下差异。
- 执行中的程序称为过程, 而;线程是流程的子集
- 进程是独立的, 而线程是进程的子集。
- 进程在内存中具有不同的地址空间, 而线程包含共享的地址空间。
- 与进程相比, 线程之间的上下文切换更快。
- 进程间通信比线程间通信慢且昂贵。
- 父进程中的任何更改都不会影响子进程, 而父线程中的更改会影响子线程。
4)通过线程间通信你了解什么?
- 同步线程之间的通信过程称为线程间通信。
- 线程间通信用于避免Java中的线程轮询。
- 线程在其关键部分暂停运行, 并允许另一个线程在要执行的同一关键部分进入(或锁定)。
- 它可以通过wait(), notify()和notifyAll()方法获得。
5)Java中的wait()方法的目的是什么?
Java中的Object类提供了wait()方法。此方法用于Java中的线程间通信。 java.lang.Object.wait()用于暂停当前线程, 并等待直到另一个线程不调用notify()或notifyAll()方法。其语法如下。
公共最终无效wait()
6)为什么必须从同步块中调用wait()方法?
我们必须调用wait方法, 否则它将抛出java.lang.IllegalMonitorStateException异常。此外, 我们需要用wait()方法与notify()和notifyAll()进行线程间通信。因此, 它必须存在于同步块中, 以进行正确和正确的通信。
7)多线程有哪些优势?
多线程编程具有以下优点:
- 多线程允许应用程序/程序始终对输入具有反应性, 即使已经运行了某些后台任务
- 多线程可以更快地执行任务, 因为线程是独立执行的。
- 由于线程共享公共内存资源, 因此多线程可更好地利用缓存内存。
- 多线程减少了所需服务器的数量, 因为一台服务器可以一次执行多个线程。
8)线程生命周期中的状态是什么?
线程在其生存期内可以具有以下状态之一:
- 新建:在这种状态下, 使用新的运算符创建了Thread类对象, 但该线程未激活。直到我们调用start()方法, 线程才会启动。
- 可运行:在这种状态下, 线程可以在调用start()方法之后运行。但是, 线程调度程序尚未选择该线程。
- 正在运行:在此状态下, 线程调度程序从就绪状态中选择线程, 并且线程正在运行。
- Waiting / Blocked(等待/阻塞):在此状态下, 一个线程未运行但仍在运行, 或者正在等待另一个线程完成。
- 终止/终止:当run()方法退出时, 线程处于终止或终止状态。
9)抢占式调度和时间分片有什么区别?
在抢占式调度下, 最高优先级的任务会一直执行, 直到进入等待状态或死机状态或存在更高优先级的任务为止。在时间分片下, 任务将执行预定义的时间片, 然后重新进入就绪任务池。然后, 调度程序根据优先级和其他因素确定下一步应执行的任务。
10)什么是上下文切换?
在上下文切换中, 存储了进程(或线程)的状态, 以便可以恢复它, 并可以稍后从同一点恢复执行。上下文切换使多个进程可以共享同一CPU。
11)区分Thread类和Runnable接口以创建线程吗?
可以通过两种方式创建线程。
- 通过扩展Thread类
- 通过实现Thread类
但是, 两种方式之间的主要区别如下:
- 通过扩展Thread类, 我们不能扩展任何其他类, 因为Java在实现Runnable接口时不允许多重继承。我们还可以扩展其他基类(如果需要)。
- 通过扩展Thread类, 每个线程创建唯一对象并在实现Runnable接口时将其与之关联。多个线程共享同一对象
- Thread类提供了各种内置方法, 例如getPriority(), isAlive等, 而Runnable接口提供了一种方法, 即run()。
12)join()方法是什么?
join()方法等待线程死亡。换句话说, 它导致当前正在运行的线程停止执行, 直到与之连接的线程完成其任务为止。 Join方法通过以下方式在Thread类中重载。
- 公共无效join()引发InterruptedException
- 公共无效连接(长毫秒)引发InterruptedException
更多细节。
13)描述sleep()方法的目的和工作方式。
java中的sleep()方法用于在特定时间内阻止线程, 这意味着它将在特定时间内暂停线程的执行。有两种方法。
语法
- 公共静态无效睡眠(长毫秒)引发InterruptedException
- 公共静态无效睡眠(长毫秒, int纳米)引发InterruptedException
sleep()方法的工作
当我们调用sleep()方法时, 它会在给定时间内暂停当前线程的执行, 并优先处理另一个线程(如果有)。此外, 当等待时间完成时, 先前的线程又将其状态从等待更改为可运行, 并进入运行状态, 整个过程一直如此, 直到执行未完成。
14)wait()和sleep()方法有什么区别?
wait() | sleep() |
---|---|
sleep()方法在Thread类中定义。 | |
sleep()方法不会释放该锁。 |
15)是否可以启动一个线程两次?
不, 我们无法重新启动线程, 因为一旦启动并执行线程, 它将进入Dead状态。因此, 如果我们尝试启动一个线程两次, 它将给出一个runtimeException” java.lang.IllegalThreadStateException”。考虑以下示例。
public class Multithread1 extends Thread
{
public void run()
{
try {
System.out.println("thread is executing now........");
} catch(Exception e) {
}
}
public static void main (String[] args) {
Multithread1 m1= new Multithread1();
m1.start();
m1.start();
}
}
输出
thread is executing now........
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:708)
at Multithread1.main(Multithread1.java:13)
更多细节。
16)我们可以调用run()方法而不是start()吗?
是的, 直接调用run()方法是有效的, 但它不能作为线程使用, 而是可以作为普通对象使用。线程之间将没有上下文切换。当我们调用start()方法时, 它将在内部调用run()方法, 该方法将为线程创建一个新堆栈, 而直接调用run()不会创建新堆栈。
更多细节。
17)守护线程如何处理?
守护程序线程是低优先级线程, 为用户线程提供后台支持和服务。如果程序仅与守护程序线程一起使用, 并且所有其他用户线程已结束/死亡, 则JVM会自动终止守护程序线程。 Thread类中有两种用于守护程序线程的方法:
- public void setDaemon(boolean status):用于标记线程守护进程线程或用户线程。
- public boolean isDaemon():它检查线程是否是守护进程。
更多细节。
18)如果启动了线程, 我们可以将用户线程设为守护线程吗?
不, 如果这样做, 它将抛出IllegalThreadStateException。因此, 我们只能在启动线程之前创建守护程序线程。
class Testdaemon1 extends Thread{
public void run(){
System.out.println("Running thread is daemon...");
}
public static void main (String[] args) {
Testdaemon1 td= new Testdaemon1();
td.start();
setDaemon(true);// It will throw the exception: td.
}
}
输出
Running thread is daemon...
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.setDaemon(Thread.java:1359)
at Testdaemon1.main(Testdaemon1.java:8)
更多细节。
19)什么是关机钩?
shutdown钩子是在JVM关闭之前隐式调用的线程。因此, 当JVM正常或突然关闭时, 我们可以使用它来执行资源清理或保存状态。我们可以使用以下方法添加关闭钩子:
public void addShutdownHook(Thread hook){}
Runtime r=Runtime.getRuntime();
r.addShutdownHook(new MyThread());
有关关闭挂钩的一些重要要点是:
- 关闭挂钩已初始化, 但只能在JVM关闭时启动。
- 关机挂钩比finalizer()更可靠, 因为关机挂钩无法运行的机会非常少。
- 可以通过调用Runtime类的halt(int)方法来停止关闭挂钩。
更多细节。
20)我们什么时候应该中断线程?
当我们想中断线程的睡眠或等待状态时, 应该中断线程。我们可以通过调用Interrupt()并抛出InterruptedException来中断线程。
更多细节。
21)什么是同步?
同步是控制多个线程对任何共享资源的访问的能力。它用于:
- 为了防止线程干扰。
- 以防止一致性问题。
当多个线程尝试执行同一任务时, 可能会产生错误的结果, 因此, 为了消除此问题, Java使用了同步过程, 该过程一次只能执行一个线程。可以通过三种方式实现同步:
- 通过同步方法
- 按同步块
- 通过静态同步
同步块的语法
synchronized(object reference expression)
{
//code block
}
更多细节。
22)同步块的目的是什么?
同步块可用于对方法的任何特定资源执行同步。一次只能在一个资源上执行一个线程, 而所有其他试图进入同步块的线程都将被阻塞。
- 同步块用于锁定任何共享资源的对象。
- 同步块的范围限于对其应用它的块。它的范围小于方法。
更多细节。
23)是否可以将Java对象锁定为由给定线程专用?
是。你可以通过将对象放在”同步”块中来锁定它。除了明确声明该线程的线程以外, 任何线程都无法访问该锁定对象。
24)什么是静态同步?
如果将任何静态方法设置为已同步, 则锁定将锁定在类上而不是对象上。如果我们在方法之前使用synced关键字, 则它将锁定对象(一个线程可以一次访问一个对象), 但是如果我们使用static sync, 则它将锁定一个类(一个线程可以一次访问一个类)。更多细节。
25)notify()和notifyAll()有什么区别?
notify()用于解除阻塞一个正在等待的线程, 而notifyAll()方法用于解除阻塞处于等待状态的所有线程。
26)什么是僵局?
死锁是一种情况, 其中每个线程都在等待其他某个等待线程所拥有的资源。在这种情况下, 线程都不执行, 也没有机会执行。而是在所有线程之间存在一个通用的等待状态。死锁是一种非常复杂的情况, 可能会在运行时破坏我们的代码。
更多细节。
27)如何检测死锁情况?如何避免呢?
我们可以通过在cmd上运行代码并收集线程转储来检测死锁情况, 如果代码中存在任何死锁, 则将在cmd上显示一条消息。
避免Java中出现死锁情况的方法:
- 避免嵌套锁:嵌套锁是导致死锁的常见原因, 因为当我们为各种线程提供锁时会发生死锁, 因此在某个特定时间只应为一个线程提供一个锁。
- 避免不必要的锁:我们必须避免不必要的锁。
- 使用线程联接:线程联接有助于等待一个线程, 直到另一个线程没有完成其执行, 这样我们就可以通过最大程度地使用join方法来避免死锁。
28)什么是Java中的线程调度程序?
在Java中, 当我们创建线程时, 它们是在Thread Scheduler(JVM的一部分)的帮助下进行监督的。线程调度程序仅负责确定应执行哪个线程。线程调度程序使用两种机制来调度线程:抢占式和时间片式。
Java线程调度程序还可以为线程确定以下内容:
- 它选择线程的优先级。
- 它确定线程的等待时间
- 它检查线程的性质
29)每个线程在多线程编程中是否都有其堆栈?
是的, 在多线程编程中, 每个线程都在内存中维护其自己或单独的堆栈区域, 因此每个线程彼此独立。
30)如何实现线程的安全性?
如果一个方法或类对象可以一次被多个线程使用而没有任何竞争条件, 则该类是线程安全的。线程安全性用于使程序在多线程编程中可以安全使用。可以通过以下方式实现:
- 同步化
- 使用挥发性关键字
- 使用基于锁的机制
- 原子包装器类的使用
31)什么是比赛条件?
竞争条件是当多个线程同时执行同时访问共享资源时在多线程编程中发生的问题。正确使用同步可以避免出现竞态条件。
32)Java中的volatile关键字是什么?
由于在所有其他线程上都可以看到一个volatile变量的更改, 因此在多线程编程中使用Volatile关键字可以实现线程安全, 因此一个线程可以一次使用一个变量。
33)你对线程池有什么了解?
- Java线程池代表一组工作线程, 它们正在等待分配任务。
- 线程池中的线程由服务提供者监督, 服务提供者从池中拉出一个线程并为其分配作业。
- 完成给定任务后, 线程再次进入线程池。
- 线程池的大小取决于为执行保留的线程总数。
线程池的优点是:
- 使用线程池可以提高性能。
- 使用线程池, 可以实现更好的系统稳定性。
并发面试问题
34)并发API的主要组件是什么?
可以使用java.util.Concurrent包的类和接口来开发并发API。 java.util.Concurrent包中包含以下类和接口。
- 执行者
- FarkJoin游泳池
- 执行器服务
- ScheduledExecutorService
- 未来
- TimeUnit(枚举)
- CountDownLatch
- 循环屏障
- 信号
- 线程工厂
- 阻塞队列
- 延迟队列
- 锁具
- 移相器
35)Java的Concurrency API中的Executor接口是什么?
包java.util.concurrent提供的Executor接口是用于执行新任务的简单接口。 Executor接口的execute()方法用于执行某些给定的命令。下面给出execute()方法的语法。
无效执行(Runnable命令)
考虑以下示例:
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
Executor e = Executors.newCachedThreadPool();
e.execute(new Thread());
ThreadPoolExecutor pool = (ThreadPoolExecutor)e;
pool.shutdown();
}
static class Thread implements Runnable {
public void run() {
try {
Long duration = (long) (Math.random() * 5);
System.out.println("Running Thread!");
TimeUnit.SECONDS.sleep(duration);
System.out.println("Thread Completed");
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
输出
Running Thread!
Thread Completed
36)什么是BlockingQueue?
java.util.concurrent.BlockingQueue是Queue的子接口, 它支持诸如在插入新值之前等待空间可用性或在从队列中检索元素之前等待队列变为非空之类的操作。考虑以下示例。
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);
Insert i = new Insert(queue);
Retrieve r = new Retrieve(queue);
new Thread(i).start();
new Thread(r).start();
Thread.sleep(2000);
}
static class Insert implements Runnable {
private BlockingQueue<Integer> queue;
public Insert(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
Random random = new Random();
try {
int result = random.nextInt(200);
Thread.sleep(1000);
queue.put(result);
System.out.println("Added: " + result);
result = random.nextInt(10);
Thread.sleep(1000);
queue.put(result);
System.out.println("Added: " + result);
result = random.nextInt(50);
Thread.sleep(1000);
queue.put(result);
System.out.println("Added: " + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Retrieve implements Runnable {
private BlockingQueue<Integer> queue;
public Retrieve(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
System.out.println("Removed: " + queue.take());
System.out.println("Removed: " + queue.take());
System.out.println("Removed: " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出
Added: 96
Removed: 96
Added: 8
Removed: 8
Added: 5
Removed: 5
37)如何通过使用BlockingQueue来实现生产者-消费者问题?
生产者-消费者问题可以通过以下方式使用BlockingQueue来解决。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ProducerConsumerProblem {
public static void main(String args[]){
//Creating shared object
BlockingQueue sharedQueue = new LinkedBlockingQueue();
//Creating Producer and Consumer Thread
Thread prod = new Thread(new Producer(sharedQueue));
Thread cons = new Thread(new Consumer(sharedQueue));
//Starting producer and Consumer thread
prod.start();
cons.start();
}
}
//Producer Class in java
class Producer implements Runnable {
private final BlockingQueue sharedQueue;
public Producer(BlockingQueue sharedQueue) {
this.sharedQueue = sharedQueue;
}
@Override
public void run() {
for(int i=0; i<10; i++){
try {
System.out.println("Produced: " + i);
sharedQueue.put(i);
} catch (InterruptedException ex) {
Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
//Consumer Class in Java
class Consumer implements Runnable{
private final BlockingQueue sharedQueue;
public Consumer (BlockingQueue sharedQueue) {
this.sharedQueue = sharedQueue;
}
@Override
public void run() {
while(true){
try {
System.out.println("Consumed: "+ sharedQueue.take());
} catch (InterruptedException ex) {
Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
输出
Produced: 0
Produced: 1
Produced: 2
Produced: 3
Produced: 4
Produced: 5
Produced: 6
Produced: 7
Produced: 8
Produced: 9
Consumed: 0
Consumed: 1
Consumed: 2
Consumed: 3
Consumed: 4
Consumed: 5
Consumed: 6
Consumed: 7
Consumed: 8
Consumed: 9
38)Java Callable接口和Runnable接口有什么区别?
想要与多个线程一起执行的类都使用Callable接口和Runnable接口。但是, 两者之间有两个主要区别:
- Callable <V>接口可以返回结果, 而Runnable接口则不能返回任何结果。
- 一个Callable <V>接口可以抛出一个已检查的异常, 而Runnable接口则不能抛出一个被检查的异常。
- 在Java 5之前不能使用Callable <V>接口, 而可以使用Runnable接口。
39)Java并发中的原子动作是什么?
- 原子动作是可以在任务的单个单元中执行的操作, 而不会干扰其他操作。
- 在任务之间不能停止原子操作。一旦开始, 它仅在任务完成后才停止。
- 增量操作(例如a ++)不允许执行原子操作。
- 基本变量的所有读写操作(long和double除外)都是原子操作。
- 易失变量(包括long和double)的所有读写操作都是原子操作。
- Atomic方法在java.util.Concurrent包中可用。
40)Java的并发API中的锁接口是什么?
java.util.concurrent.locks.Lock接口用作同步机制。它的工作原理类似于同步块。锁定和同步块之间有一些区别, 如下所示。
- 锁接口提供了对等待线程进行访问的顺序的保证, 而同步块则不能保证。
- 如果未授予锁, 而同步块不提供锁, 则锁接口提供超时选项。
- 可以使用不同的方法调用Lock接口的方法, 即Lock()和Unlock(), 而单个同步块必须完全包含在一个方法中。
41)解释ExecutorService接口。
ExecutorService接口是Executor接口的子接口, 并添加了用于管理生命周期的功能。考虑以下示例。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
ExecutorService e = Executors.newSingleThreadExecutor();
try {
e.submit(new Thread());
System.out.println("Shutdown executor");
e.shutdown();
e.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
System.err.println("tasks interrupted");
} finally {
if (!e.isTerminated()) {
System.err.println("cancel non-finished tasks");
}
e.shutdownNow();
System.out.println("shutdown finished");
}
}
static class Task implements Runnable {
public void run() {
try {
Long duration = (long) (Math.random() * 20);
System.out.println("Running Task!");
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
输出
Shutdown executor
shutdown finished
42)关于线程的同步编程和异步编程之间有什么区别?
同步编程:在同步编程模型中, 为完成任务分配了一个线程, 因此线程开始对其进行处理, 并且只有在结束分配的任务后, 该线程才可用于其他任务。
异步编程:在异步编程中, 一个作业可以由多个线程完成, 因此它提供了各个线程的最大可用性。
43)你对Java中的Callable and Future有什么了解?
Java可调用接口:在Java5中, 可调用接口由包java.util.concurrent提供。它类似于Runnable接口, 但是它可以返回结果, 并且可以引发Exception。它还提供了用于执行线程的run()方法。 Java Callable可以使用Generic返回任何对象。
语法
公共接口Callable <V>
Java Future接口:Java Future接口提供并发过程的结果。 Callable接口返回java.util.concurrent.Future的对象。
Java Future提供以下实现方法。
- cancel(boolean mayInterruptIfRunning):用于取消分配任务的执行。
- get():如果执行未完成, 则等待时间, 然后检索结果。
- isCancelled():如果完成之前取消了任务, 则返回布尔值, 因为它返回true。
- isDone():如果作业成功完成, 则返回true, 否则返回false。
44. ScheduledExecutorService和ExecutorService接口之间有什么区别?
ExecutorServcie和ScheduledExecutorService都是java.util.Concurrent包的接口, 但是ScheduledExecutorService提供了一些其他方法来执行Runnable和Callable任务, 这些任务具有延迟或每个固定时间段。
45)在Java中定义FutureTask类吗?
Java FutureTask类提供Future接口的基本实现。只有完成一项任务的执行才能获得结果, 如果未完成计算, 则get方法将被阻塞。如果执行完成, 则无法重新启动, 也无法取消。
语法
公共类FutureTask <V>扩展对象实现RunnableFuture <V>
1 2 3 4 5 6 7 8
Java OOP面试问题 |
Java字符串和异常面试问题 |
JDBC面试问题 |
JSP面试问题 |
休眠面试问题 |
SQL面试题 |
Android面试题 |
MySQL面试问题 |
1)在对象类中定义了wait()方法。
2)wait()方法释放锁定。
Java基础面试问题
Java多线程面试问题
Java Collection面试题
Servlet面试问题
春季面试问题
PL / SQL面试问题
Oracle面试问题
SQL Server面试问题
评论前必须登录!
注册