上一篇传送门:点我
java面试中,并发相关的一些面试题整理,很多部分仅为个人理解,如有问题欢迎讨论指正(待补充)。
并行与并发有什么区别?
并行和并发都是指多个任务同时执行的概念,但是 它们之间有着明显的区别。
并行是指多个任务在同一时刻同时运行,通常会需要使用多个处理器或者多核处理器来实现;而并发指的是多个任务的执行是交替进行的,一个任务执行一段时间后,再去执行另一个任务,它是通过操作系统的协作调度来实现各个任务的切换,从而达到看上去同时进行的效果。
举个例子,假设现在有一个四核的CPU,并行处理就可以让这四个处理器核心同时处理四个不同的任务,每个处理器核心都在独立地工作。而并发处理则是让这四个任务在一个处理器核心上轮流执行,每个任务得到一点时间片来运行,然后就被切换到下一个任务,此时同一时刻只有一个任务在执行。
说说什么是进程与线程 / 说说进程与线程之间的区别
进程和线程是操作系统中概念,用于描述程序运行时的执行实体。
进程是一个程序在执行过程中的一个实例,可以并发地执行多个任务,每个进程都有自己独立的地址空间,也就是说它们不能直接共享内存,两个进程之间需要通过进程间通信(IPC)来交换数据。
线程是进程中的一个执行单元,一个进程里可以包含多个线程,同一个进程中的多个线程可以并发地执行多个任务,并且这些线程共享进程的内存空间。
线程相比于进程,线程的创建和销毁开销较小,上下文切换开销也较小,因此线程是实现多任务并发的一种更轻量级的方式。
Java线程的创建方式主要有哪几种?
Java中创建线程主要有三种方式。它们分别是继承Thread类、实现Runnable接口、实现Callable接口,但它们的底层实际上都是基于Runnable接口。
继承Thread类:
public class MyThread extends Thread{
public static void main(String[] args){
MyThread thread = new MyThread();
thread.start();
}
@Override
public void run(){
System.out.println("Hello MyThread!");
}
}
总结: 继承Thread类重写的是run()方法,不是start()方法,但是占用了继承的名额,由于Java中的类是单继承的,这会导致该线程无法继承其他父类。
实现Runnable接口:
public class MyRunnable implements Runnable{
public static void main(String[] args){
Thread thread = new Thread(new MyRunnable());
thread.start();
}
@Override
public void run(){
System.out.println("Hello MyRunnable!");
}
}
总结: 实现Runnable接口,实现run()方法,使用依然要用到Thread,这种方式相比直接继承Thread类,还可以继承其他类,所以这种方法更常用。
实现Callable接口:
public class MyCallable implements Callable<String> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
@Override
public String call(){
return"Hello MyCallable!";
}
}
总结: 实现Callable接口需要实现call()方法,得使用Thread+FutureTask配合,这种方法可以拿到异步执行任务的结果。
Java线程是调用什么方法来启动,为什么?
Java线程是通过调用线程的start()方法来启动的。当创建了一个线程对象后,该线程并不会自动开始执行,而是处于一种新建状态。为了启动线程并执行线程run()方法中的代码,需要先调用该线程的start()方法,并且对于一个线程对象,start()方法只能被执行一次。
调用start()方法实际上是在告诉JVM,我们希望启动一个新的线程来执行该线程对象中的run()方法。start()方法内部会进行一些必要的初始化工作,如分配系统资源,并准备将线程放入就绪状态。一旦线程获得CPU时间片,就会从就绪状态进入运行状态,开始执行run()方法中的代码。
需要注意: run方法仅仅是线程类中的一个普通方法而已。调用run()方法并不会创建一个新的线程,而只是在当前线程中执行了run()方法而已,这就像调用普通的方法一样。
Java线程有哪些常用的调度方法?
Java线程常用的调度方法一般有线程等待、线程通知、线程休眠、线程让步、线程中断五种。
线程等待: 当前线程调用Object类的wait() 方法后,会使线程进入等待状态,直到其他线程调用此线程对象的notify()或notifyAll() 方法来唤醒它。这个方法通常用在多线程同步中,以确保线程按照特定的顺序访问共享资源。wait方法也可以有超时参数timeout,如果线程调用这个方法后,没有在指定的timeout时间内被唤醒,那么这个方法会因为超时返回。
- 在线程等待中还有一个**join()**方法,它能够让父线程等待子线程结束之后继续执行,即当我们调用某个线程的join()方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。
线程通知: 一个线程可以通过调用Object类的notify() 或notifyAll() 方法来唤醒在该对象上等待的其他线程。当调用的是notify()方法时,如果此时有多个线程在此对象上等待,则会随机唤醒一个线程,而notifyAll()则是唤醒此对象上等待的所有线程。
线程休眠: 通过调用Thread类下的sleep(long) 方法,使当前线程转到超时等待阻塞状态,long参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,线程则会自动转为就绪状态。
线程让步: 通过调用Thread类下的yield() 方法,暂停当前正在执行的线程对象,把执行的机会让给相同或更高优先级的线程。
线程中断: 通过调用Thread类下的interrupt() 方法,将该线程的中断标志为设为true。中断的结果线程是死亡,还是等待新的任务或是继续运行下一步,就取决于程序本身。同时可以通过调用isInterrupted() 或是interrupted() 方法去判断某个线程是否已被发送过中断请求,其中isInterrupted() 方法不会清除中断标志位,而interrupted() 方法如果发现当前线程被中断,则会清除线程中断标记,并将中断标记设置为false。
Java线程有多少种状态?
Java线程的状态分为初始状态、运行状态(可以分为就绪和运行中)、阻塞状态、等待状态、超时等待和终止状态六种状态。
初始状态 (NEW):线程被构建,但是还没有调用 start 方法;
运行状态 (RUNNABLE):该状态代表线程已经准备运行或者正在运行。一般在调用 start() 方法后,该线程就处于该状态 ;
阻塞状态 (BLOCKED):这种状态一般发生在多个线程进行工作时,假如有两个线程:线程1和线程2。当通过先启动线程1后启动线程2时候。此时的线程1处于运行状态,而线程2处于阻塞状态;
等待状态 (WAITING):线程在调用其它线程的join()方法、调用wait()方法等操作时,会使该线程会进入等待状态。进入这个状态后的线程需要等待其他线程做出一些特定动作(通知或中断)。
超时等待状态 (TIMED_WAITING):超时等待状态用在于线程使用 sleep() 方法时,线程就处于超时等待状态。直到 sleep() 方法执行完毕,线程才会被唤醒。
终止状态 (TERMINATED):当线程执行完毕。这个线程就死亡了,此时就是终止状态。
图片来源:https://blog.csdn.net/qq_33996921/article/details/106357703
什么是线程上下文切换?
线程上下文切换指的是在多线程运行时,操作系统从当前正在执行的线程中保存它的上下文信息(包括当前线程的寄存器、程序指针、栈指针等状态信息),并将另一个等待执行的线程的上下文信息恢复到该线程中,从而实现线程直接的切换。
线程有哪些通信方式?
线程间通信是指在多线程编程中,各个线程之间共享信息或者协同完成某一任务的过程。常用的线程间通信方式一般有以下几种:
1.共享变量:共享变量是指多个线程都可以访问和修改的变量。它们通常是在主线程中创建的。多个线程对同一个共享变量进行读写操作的时候,可能会导致数据错误或程序异常。需要使用同步机制如synchronized、Lock等来保证线程安全;
2.管道通信:管道是一种基于文件描述符的通信机制,形成一个单向通信的数据流管道。它通常用于只有两个进程或线程之间的通信。其中一个进程将数据写入到管道(管道的输出端口),另一个进程从管道的输入端口读取数据;
3.信号量:信号量(semaphore)是一种计数器,用于控制多个线程对资源的访问。当一个线程需要访问资源时,它需要申请获取信号量,如果信号量的计数器大于0,则可以访问资源,否则该线程就会等待。当线程结束访问资源后,需要释放信号量,并将计数器加1;
4.条件变量: 条件变量是一种通知机制,用于在多个线程之间传递状态信息和控制信息。当某个线程需要等待某个条件变量发生改变时,它可以调用**wait()方法挂起,并且释放所占用的锁。当某个线程满足条件后,可以调用notify()或者signal()**方法来通知等待该条件变量的线程继续执行。