🔥系列专栏:Java面试专题
1.线程和进程的区别
线程和进程都是操作系统中的概念,它们的主要区别如下:
-
资源分配:进程是操作系统中的资源分配的基本单位,每个进程都有独立的内存空间、文件句柄等资源,而线程是进程中的一个实体,它与其他线程共享同一进程的资源。
-
调度:进程拥有自己的调度器,由操作系统负责进行调度,而线程则由进程的调度器进行调度,操作系统不能直接对线程进行调度。
-
运行效率:线程相比进程来说更加轻量级,创建和销毁的开销小,切换上下文也更快,因此线程的运行效率更高。
-
通信方式:进程之间的通信需要经过操作系统的管道或共享内存等机制,而线程之间可以直接共享同一进程的内存空间,因此线程间通信更加快捷。
2.并行与并发的区别
在多核CPU的情况下:
并发是同一时间应对多件事情的能力,例如:多个线程轮流使用一个CPU。
并行是同一时间动手做多件事情的能力,例如:4核CPU同时执行4个线程。
3.创建线程的方式有哪些
- 通过继承Thread类创建线程。需要重写run()方法,启动线程时,通过调用start()方法启动。
- 通过实现Runnable接口创建线程。需要重写run()方法,启动线程时,将自定义类的实例作为一个参数,调用Thread的构造方法,得到一个线程实例,再调用start()方法启动。
- 通过实现Callable接口创建线程。需要重写call()方法,启动线程时,需要新建一个Callable的实例,再用FutureTask实例包装它,最终再包装成Thread实例,调用start()方法启动,并且可以通过FutureTask的get()方法来获取返回值。
- 使用线程池,可以通过调用Executors类的静态方法创建线程池,然后向线程池中提交任务来创建线程。
4.Runnable和Callable有什么区别
-
Runnable不会返回结果或抛出异常,而Callable会返回结果或抛出异常。
-
Callable接口中定义的方法是call()方法,而Runnable接口中定义的方法是run()方法。
-
Callable可以通过Future接口来获取执行结果,而Runnable没有这样的机制。
-
Callable的call()方法可以抛出异常,而Runnable的run()方法不能抛出异常。
5.run()和start()有什么区别
-
run()方法定义了线程的主要内容,是实际执行的代码。但是,当使用run()方法时,代码执行在当前线程中,并没有创建新线程。可以被调用多次(就和普通方法一样)
-
start()方法启动一个新线程来执行run()方法中的代码。当使用start()方法时,代码将在新线程中执行,并且在主线程中继续执行其他代码。只能被调用一次。
6.线程包括那些状态
Java线程包括如下状态:
-
新建状态(New):当线程对象被创建时,它处于新建状态。
-
可执行状态(Runnable):当调用start()方法后,线程开始执行,进入可运行状态。可运行状态包括正在运行和就绪两种情况。
-
阻塞状态(Blocked):如果没有获取锁(synchronized或lock)进入阻塞状态,获得锁再切换为可执行状态
-
等待状态(Waiting):如果线程调用wait()方法进入等待状态,其他线程调用notify()唤醒后可以切换为可执行状态
-
计时等待状态(Timed Waiting):如果线程调用sleep(100)方法,进入计时等待状态,到时间后可切换为可执行状态
-
终止状态(Terminated):线程执行完成或者发生了未捕获的异常,进入终止状态。
7.新建T1、T2、T3三个线程,如何保证他们顺序执行
可以使用线程.join()方法来保证三个线程的顺序执行。具体实现方法如下:
java
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 线程T1的代码
System.out.println("Thread T1");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 线程T2的代码
System.out.println("Thread T2");
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
// 线程T3的代码
System.out.println("Thread T3");
}
});
// 启动线程,并使用join()保证顺序执行
t1.start();
t1.join(); // 等待t1执行完毕
t2.start();
t2.join(); // 等待t2执行完毕
t3.start();
t3.join(); // 等待t3执行完毕
}
}
8.notify()和notifyAll()有什么区别
notify()
和notifyAll()
都是Java中用于线程同步的方法,但它们的使用方式和作用有所不同。
notify()
:这个方法用于唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,它将唤醒其中一个线程。被唤醒的线程将获得锁,然后执行。notifyAll()
:这个方法用于唤醒在此对象监视器上等待的所有线程。当一个线程在对象上调用wait()
方法时,它会在那个对象上等待,直到其他线程在该对象上调用notify()
或notifyAll()
方法。当notifyAll()
方法被调用时,所有在该对象上等待的线程都会被唤醒,所有的这些线程将尝试获取锁,然后其中的一个将获得锁并执行。
简单来说,notify()
用于唤醒单个线程,而notifyAll()
用于唤醒所有等待的线程。在多线程编程中,合理使用这两个方法可以帮助我们更好地控制线程的执行顺序和同步。
9.在java中wait和sleep方法的不同
在Java中,wait()
和sleep()
方法都是用来暂停线程的执行,但是它们的使用方式和效果有很大的不同。
sleep()
方法:这个方法是Thread
类的一个静态方法。它会使当前线程暂停执行一段时间,这段时间内线程不会消耗CPU资源。在指定的时间间隔之后,线程会重新启动。注意,sleep()
方法需要一个参数,这个参数是线程暂停的时间,单位是毫秒。
java
Thread.sleep(2000); // 线程会暂停2秒
wait()
方法:这个方法是Object
类的一个成员方法。它会使当前线程等待,直到其他线程调用同一个对象的notify()
或notifyAll()
方法。在等待期间,线程不会消耗CPU资源。注意,wait()
方法需要在对象上调用,并且需要在一个同步上下文中。也就是说,它通常被包含在一个synchronized
块或者方法中。
java
synchronized (object) {
while (<condition does not hold>)
object.wait();
// perform action appropriate to condition
}
在这个例子中,线程会等待,直到<condition does not hold>
变为false
。然后,线程会继续执行。
总的来说,sleep()
和wait()
的主要区别在于:
sleep()
是静态方法,可以在任何地方调用,但它不会检查任何条件;而wait()
是对象的方法,必须在同步上下文中调用,它会检查一个条件并决定是否等待。sleep()
会导致线程阻塞一段时间,这段时间内线程不会消耗CPU资源;而wait()
会导致线程进入等待状态,直到其他线程调用同一个对象的notify()
或notifyAll()
方法。在这段时间内,线程不会消耗CPU资源。sleep()
和wait()
都需要一个时间参数,但是这个参数的含义不同。对于sleep()
,它是暂停的时间;对于wait()
,它是等待的最大时间(毫秒),如果在这个时间内没有接收到通知,那么线程会重新竞争锁并重新检查条件。
10.如何停止一个正在运行的线程
①使用一个标志位来通知线程可以安全地终止其执行
java
class MyThread extends Thread {
private volatile boolean running = true;
public void run() {
while (running) {
// ... do some work ...
}
}
public void shutdown() {
running = false;
}
}
在这个例子中,当shutdown()
方法被调用时,线程将停止其执行。当然,线程应该定期检查running
变量的值,并在可能的情况下安全地退出循环。这样,线程就可以在适当的时候结束其执行。
②使用stop方法强行终止(不推荐,方法已经作废)
③使用interrupt方法中断线程
在Java中,你可以使用interrupt
方法来尝试中断一个线程。但是,你需要注意的是,interrupt
方法并不会立即终止线程,而是会设置一个中断标记,你需要在线程的中断处理代码中响应这个中断。
以下是一个简单的例子,说明如何中断一个线程:
java
public class MyThread extends Thread {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
// 线程的逻辑...
}
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
// 在需要的时候中断线程
myThread.interrupt();
}
}
在这个例子中,MyThread
类继承了Thread
,并重写了run
方法。在run
方法中,我们在循环中检查线程是否被中断。如果线程被中断,isInterrupted
方法会返回true
,这样我们就可以退出循环。
在主方法中,我们创建了一个MyThread
对象,并启动了线程。然后,我们使用interrupt
方法来中断线程。
请注意,这只是一个简单的例子。在实际的程序中,你可能需要在更复杂的逻辑中处理线程的中断。例如,如果你的线程是在一个阻塞操作(如Thread.sleep
或Object.wait
)中,那么当调用interrupt
方法时,线程会收到一个InterruptedException
。你需要在代码中处理这个异常,并响应中断。