龙空技术网

Java多线程面试题,面试官最喜欢听到的答案

千锋教育 49

前言:

现在兄弟们对“java 面试逻辑题”大致比较注意,看官们都需要剖析一些“java 面试逻辑题”的相关内容。那么小编同时在网络上收集了一些对于“java 面试逻辑题””的相关内容,希望咱们能喜欢,我们一起来学习一下吧!

作为面试官,肯定喜欢听面试者用自己的话来解释面试题,因为这可以展示面试者对于这个概念的理解程度以及能否清晰地表达技术概念

今天小岳主要给大家带来的是:什么是Java多线程、如何创建线程、线程都有哪些状态以及线程的生命周期和线程安全这几道面试题。接下来就请大家跟我一起来看看这几道java多线程面试题吧,保证你能学会!

1.什么是多线程?

多线程是一种编程技术,可以让计算机同时处理多个任务,这些任务可以在同一时间内运行,提高了程序的效率。

类比于日常生活,就像一个人同时可以做多件事情,比如说同时听音乐、看视频、写作业等

比如你在下载一个很大的文件,如果你使用单线程下载,那么你只能等待文件下载完成后才能去做其他事情。但是如果你使用多线程下载,那么你可以同时开启多个线程下载这个文件的不同部分,这样可以加快下载速度,同时也可以让你在下载的同时做其他事情,提高了效率。

贴合生活中的例子来跟大家做解释,是不是发现多线程的概念也没有那么难懂,所以记住一句话,代码是死的,人是活得,是代码服务我们,是这些技术来优化我们的生活,所以一定要活学活用!

2. Java中如何创建线程?

接下来我们将用生活实例为大家讲解,生动易懂!

在Java中创建线程有两种方式:

继承Thread类

实现Runnable接口

下面我们用一个最简单的生活案例给大家详细解释一下这两种方式吧

假设:

你正在家里做晚饭,现在有两个任务需要同时进行:烧开水和煮面条

这个时候,你可以选择自己完成这两个任务,也可以选择喊你的女儿帮你完成

如果选择让女儿来帮忙,那么就可以将孩子看作一个线程。这个线程需要完成烧开水和煮面条两个任务。在Java中,你可以通过继承Thread类来创建一个线程,具体的代码如下:

public class CookThread extends Thread {    private String task;    public CookThread(String task) {        this.task = task;    }    public void run() {        if (task.equals("boilWater")) {            boilWater();        } else if (task.equals("cookNoodle")) {            cookNoodle();        }    }    public void boilWater() {        System.out.println("开始烧开水...");        // 烧开水的逻辑        System.out.println("烧开水完成!");    }    public void cookNoodle() {        System.out.println("开始煮面条...");        // 煮面条的逻辑        System.out.println("煮面条完成!");    }}

在上面这个代码中,我们定义了一个CookThread类,这个类继承了Thread类。我们通过重写run()方法来实现线程的逻辑。在run()方法中,我们判断当前线程需要执行的任务,然后调用相应的方法来完成任务。boilWater()方法和cookNoodle()方法分别表示烧开水和煮面条的逻辑。

现在,我们可以通过创建两个CookThread实例来启动两个线程,代码如下:

CookThread boilWaterThread = new CookThread("boilWater");CookThread cookNoodleThread = new CookThread("cookNoodle");boilWaterThread.start();cookNoodleThread.start();

通过调用start()方法来启动线程,线程会在后台运行,执行对应的任务

另外一种创建线程的方式是:实现Runnable接口。

和上面的例子类似,你可以让你的孩子来帮你做两个任务。不过这次,不再是将孩子看作一个线程,而是将每个任务看作一个线程。

在Java中,我们可以通过实现Runnable接口来创建一个线程,具体的代码如下:

public class CookTask implements Runnable {    private String task;    public CookTask(String task) {        this.task = task;    }    public void run() {        if (task.equals("boilWater")) {            boilWater();        } else if (task.equals("cookNoodle")) {            cookNoodle();        }    }    public void boilWater() {        System.out.println("开始烧开水...");        // 烧开水的逻辑

以上就是针对如何创建线程做出的简单汇总,大家可以继续练习了哦!

3.线程有哪些状态?

NEW(新建状态):当线程对象被创建但还未调用start()方法时,它处于新建状态。

示例代码:

Thread thread = new Thread();

Runnable(可运行状态):当调用线程对象的start()方法时,线程进入可运行状态。在可运行状态下,线程可以被线程调度器选中,执行线程的run()方法。

示例代码:

Thread thread = new Thread(() -> {    System.out.println("Hello, world!");});thread.start(); // 线程进入可运行状态

Blocked(阻塞状态):当线程试图获取一个已经被其他线程持有的锁时,它将进入阻塞状态。在阻塞状态下,线程不会被线程调度器选中,知道它获取到锁。

示例代码:

Object lock = new Object();Thread thread1 = new Thread(() -> {    synchronized (lock) {        // 执行同步代码块    }});Thread thread2 = new Thread(() -> {    synchronized (lock) {        // 执行同步代码块    }});thread1.start(); // 线程1进入可运行状态thread2.start(); // 线程2进入阻塞状态,因为锁被线程1持有

Waiting(等待状态):当线程调用Objece.wait()、Thread.join()或LockSupport.park()方法时,它将进入等待状态。在等待状态下,线程不会被线程调度器选中,直到它被唤醒。

Object lock = new Object();Thread thread1 = new Thread(() -> {    synchronized (lock) {        try {            lock.wait(); // 线程1进入等待状态        } catch (InterruptedException e) {            e.printStackTrace();        }    }});Thread thread2 = new Thread(() -> {    synchronized (lock) {        lock.notify(); // 唤醒线程1    }});thread1.start(); // 线程1进入等待状态thread2.start(); // 唤醒线程1,使其进入可运行状态

Timed-Waiting(计时等待状态):

当线程调用

Thread.sleep().Objece.wait(long).Thread.join(long)或LockSupport.parkNanos()方法时,它将进入计时等待状态。

在计时等待状态下,线程不会被线程调度器选中,在指定时间后,线程自动唤醒。

示例代码:

Thread thread = new Thread(() -> {    try {        Thread.sleep(1000); // 线程进入计时等待状态    } catch (InterruptedException e) {        e.printStackTrace();    }});thread.start(); // 线程进入可运行状态

Terminated(终止状态):当线程的run()方法执行完毕时,线程将进入终止状态。

4.线程的生命周期是怎样的?

线程的生命周期包括五个不同的阶段:新建、就绪、运行、阻塞和死亡。下面小岳会结合代码案例来分析线程的生命周期。

代码示例一:

public class ThreadStateExample implements Runnable {    @Override    public void run() {        try {            // 线程运行5秒钟            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public static void main(String[] args) {        Thread thread = new Thread(new ThreadStateExample());        // 线程新建状态        System.out.println("Thread state: " + thread.getState());        // 启动线程        thread.start();        // 线程就绪状态        System.out.println("Thread state: " + thread.getState());        // 线程运行状态        while (thread.isAlive()) {            System.out.println("Thread state: " + thread.getState());        }        // 线程死亡状态        System.out.println("Thread state: " + thread.getState());    }}

在上面的代码中,首先创建一个ThreadStateExample对象,并将其传递给Thread构造函数来创建一个新线程。在main方法中,首先输出线程的状态,这时线程处于新建状态。然后调用thread.start()方法启动线程,线程进入就绪状态。接着使用while循环来检测线程是否仍在运行,如果线程仍在运行,就输出线程的状态,这时线程已经处于死亡状态。

当线程处于阻塞状态时,可以使用以下方法之一使其恢复。

sleep(long millis): 使当前线程休眠指定的毫秒数。

wait(): 当前线程等待另一个线程调用notify()或notifyAll()方法。

join(): 等待线程执行完成。

park(): 阻塞当前线程,直到调用unpark(Thread thread)方法。

代码示例二:

假设我们要开发一个简单的多线程程序,用来计算1到1000之间的所有偶数的和。我们可以创建一个名为"EvenSumThread"的线程类,继承自Thread类,并实现run()方法,用来执行线程的任务。具体代码如下:

public class EvenSumThread extends Thread {    private int sum;    public void run() {        for (int i = 2; i <= 1000; i += 2) {            sum += i;            System.out.println("Thread " + Thread.currentThread().getId() + " is running, sum is " + sum);        }    }    public int getSum() {        return sum;    }}

在这个程序中,我们在EvenSumThread类中定义了一个私有变量sum,用来记录1到1000之间所有偶数的和。在run()方法中,我们使用for循环遍历1到1000之间的所有偶数,并将它们相加,同时在控制台上输出线程的ID和当前的sum值。在getSum()方法中,我们可以获取计算结果。

现在,我们来分析一下这个多线程程序中线程的生命周期:

新建阶段(New):当我们创建EvenSumThread对象时,线程进入了新建阶段,此时还没有开始执行线程任务。

EvenSumThread evenSumThread = new EvenSumThread();

就绪阶段(Runnable):当我们调用evenSumThread.start()方法时,线程进入了就绪阶段,此时线程已经准备好执行,但是还没有得到CPU的时间片。

evenSumThread.start();

运行阶段(Running):当线程获得了CPU时间片并开始执行run()方法时,线程进入了运行阶段。

阻塞阶段(Blocked):在本例中,线程不会进入阻塞阶段,因为它没有被阻塞。

死亡阶段(Terminated):当线程完成了它的任务或者抛出了未捕获的异常时,线程进入了终止阶段。

System.out.println("The sum of even numbers from 1 to 1000 is " + evenSumThread.getSum());

以上就是一个多线程程序中线程的生命周期,从新建、就绪、运行、阻塞到终止,每个阶段都有不同的特点和表现。

5.什么是线程安全?

线程安全指多个线程同时访问同一个共享资源时,不会产生不一致的结果或者出现异常的情况。Java提供了多种机制来实现线程安全,例如使用同步代码块、同步方法、使用线程安全的集合等。下面给出一个生活案例,通俗易懂的跟大家解释一番究竟什么是线程安全?如图所示:

下面给出一个生活案例,通俗易懂的跟大家解释一番究竟什么是线程安全?如图所示:

在Java中,我们用代码表达就如下:

public class Counter {    private int count;    public synchronized void increment() {        count++;    }    public synchronized void decrement() {        count--;    }    public synchronized int getCount() {        return count;    }}

在这个示例中,Counter类维护一个计数器变量count,提供了三个同步方法:increment()、decrement()和getCount()。这些方法都使用了synchronized关键字,因此它们在任何时候都只能由一个线程访问。这样,即使有多个线程同时访问Counter对象,也不会出现线程安全问题。

需要注意的是,使用synchronized关键字会降低程序的执行效率,因为每次访问同步方法或同步代码块都需要获取锁,这会导致线程的阻塞。因此,在实现线程安全的同时,还应该考虑性能问题。

以上就是关于java线程安全的简单介绍,大家一定不要死记硬背面试题哦,可以结合生活案例以及代码来分析理解它,这样才能有效加深印象哦!

6. 总结

好啦!以上就是针对部分面试题来给大家做出的简单介绍,当然都是比较结合生活和通俗易懂的介绍,希望可以对大家的面试带来帮助哦!

标签: #java 面试逻辑题