面试专题 | Java多线程部分在面试过程中常会问到的问题整理

目录

问题整理

1、什么是线程和进程,他们的区别和联系?

2、线程创建的方式有几种?分别是什么?如何启动一个线程?

3、线程的有几种状态?分别是什么?

4、线程间几种状态的转换?

5、线程的优先级

6、用户线程和守护线程的区别

7、ReentrantLock可中断锁

8、锁Synchronized和reentranLock的对比

9、怎么让线程按照顺序执行

10、设计线程的标记位可以在任何时间break掉

11、死锁产生条件

12、怎么设计一个死锁

13、LinkedBlockingQueue和ArrayBlockingQueue的区别

14、sleep() 和 wait() 有什么区别

15、请说出你所知道的线程同步的方法。

16、什么是线程饿死,什么是活锁?

17、volatile 变量是什么?volatile 变量和 atomic 变量有什么不同

18、volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗?

19、什么是线程池

20、为什么使用线程池?线程池的作用

21、几种常见的线程池及使用场景

22、线程池中的几种重要的参数

23、线程池的工作流程

 


问题整理

1、什么是线程和进程,他们的区别和联系?

进程是指运行的程序,比如计算机上运行起来的一个应用就是一个进程。进程是由线程组成的,一个进程可以包含多个线程,比如QQ聊天进程由一个发送线程+一个接收线程组成。而线程只是CPU的运行单位。

区别:

  1. 进程是资源分配的最小单位,而线程是程序执行的最小单位;
  2. 资源:进程是资源分配的单位,系统分配资源的时候给每个进程单独资源,但线程是资源共享的,一个进程下的线程共享本进程的资源;
  3. 地址空间:每个进程都有一块自己的独立地址空间,而线程只是最为一个入口,地址空间是共享的;
  4. 进程之间相互独立,一个进程崩溃不会影响其他进程;但同一进程下的线程崩溃会影响本进程下的其他线程;

联系:

  1. 一个进程可以包含多个线程;
  2. 在没有实现线程的操作系统中,进程是资源分配的基本单位,又是调度的基本单位,它是系统中并发执行的单元。在实现线程的操作系统中,进程是资源分配的基本单位,但线程是基本调度单元,是系统并发执行的单元。

2、线程创建的方式有几种?分别是什么?如何启动一个线程?

答案参考上篇博客:https://blog.csdn.net/weixin_44187963/article/details/103459524

3、线程的有几种状态?分别是什么?

线程有6种状态,分别是:

new状态:新建了一个线程对象,但还没有启动
runnable(就绪状态):线程对象创建后调用start方法启动线程,此时线程就处于runnable状态
blocked(阻塞状态):1)等待一个监视器锁来进入同步代码块或同步方法
                  2)调用Object.wait()方法,此时需要notify/notifyAll去唤醒它
waiting(等待状态):Object.wait()/notify/notifyAll,Thread.join(), LockSupport.park()
timed_waiting:Object.wait()/notify/notifyAll,Thread.join(), LockSupport.park()
terminated(终止状态):线程执行完了之后就会进入到终止状态

4、线程间几种状态的转换?

5、线程的优先级

1)线程优先级的取值范围:1到10

默认优先级=5 优先级由高到低10 9...2 1 优先级高的率先完成任务

2)设定线程的优先级:thread.setPriority(1)

3)优先级特性:

规则性:优先级高的率先完成任务的纪律更大;

随机性:CPU会尽量将资源分配给优先级高的,所以我们看到的结果并不是一定优先级高的先执行完;

继承性:在A线程下创建一个B线程,那么B线程的优先级==A线程的优先级

6、用户线程和守护线程的区别

结束时机:用户线程结束时run方法执行完;而守护线程是等待所有用户线程执行完毕之后;

守护线程使用场景:---GC:Java对象(垃圾)回收机制

7、ReentrantLock可中断锁

ReentrantLock实现的前提就是AbstractQueuedSynchronizer,简称AQS,是java.util.concurrent的核心。AQS是基于FIFO队列的实现AQS中的队列是由 Node节点组成的双向链表实现的。所有的操作都是在这个AQS队列当中。如果一个线程获取锁成功那就成功了,如果失败了就将其放入等待队列当中。

8、锁Synchronized和reentranLock的对比

1)本质:synchronized是一个关键字,reentrantLock是一个类;

2)加锁方式:synchronize只提供阻塞的加锁方式,加锁解锁过程是由jvm控制;reentrantLock提供更加丰富的加锁方式,加锁解锁需要手动调用相应的方法;

3)公平性:synchronized:非公平的锁;reentrantLock:既可以实现公平的锁、也可以实现非公平的锁:实现方法为改变改变构造函数中的参数,传true实现为公平,不传或者传false就实现为非公平的;

4)synchronized:是非重入锁; reentrantLock:是重入锁;

5)实现原理(都是用了CAS操作):synchronized是mark word 和 monitor,reentrantLock借助state 和AQS队列;

6)和读写锁的对比:

synchronized和读写锁对比:读读不互斥,读写互斥,写写互斥;

reentrantLock和读写锁对比:读读不互斥,读写互斥,写写互斥;

9、怎么让线程按照顺序执行

优先级不可以,所以用join()插队的方式实现线程的执行顺序

10、设计线程的标记位可以在任何时间break掉

线程正常结束是:执行完run

设置断点结束一个线程的时候,设置一个flag变量,一定得加volatile--强制进行同步,监听工具 private volatile boolean flag = false;

11、死锁产生条件

1)互斥:A线程和B线程,它们同时请求一部分资源,只有其中一个能拿到

2)请求保持:A线程和B线程,A和B都需要拿到1和2资源才能执行,所以AB分别拿了12那么AB都处于请求保持

3)循环等待:假如AB分别拿了12,那么它们每人占用一个资源同时等待另一个资源释放,所以都处于循环等待

4)不可分割:A在拿到资源之后如果没有完成任务之前资源不能被别的线程抢占

12、怎么设计一个死锁

syncharnized实现,需要有两个锁 lock1和lock2

1.首先需要有两个或两个以上的线程和资源

2.A和B都要保证把lock1和lock2都拿到才能执行后续代码

3.A先拿到lock1且B先拿到lock2

13、LinkedBlockingQueue和ArrayBlockingQueue的区别

(1)方法:实例化对象时,ArrayBlockingQueue必须传参数,LinkedBlockingQueue可不传参数。

(2)底层:ArrayBlockingQueue底层是数组(适合随机访问,有容量),LinkedBlockingQueue底层是链表(适合插入删除,可制定上限);

(3)锁:ArrayBlockingQueue用的是单锁,LinkedBlockingQueue用的是双锁;

(4)效率:ArrayBlockingQueue用的是单锁效率较低,LinkedBlockingQueue用的是双锁效率较高;

14、sleep() 和 wait() 有什么区别

sleep 就是正在执行的线程主动让出 cpu,cpu 去执行其他线程,在 sleep 指定的时间过后,cpu 才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep 方法并不会释放锁,即使当前线程使用 sleep 方法让出了 cpu,但其他被同步锁挡住了的线程也无法得到执行。wait 是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了 notify 方法(notify 并不释放锁,只是告诉调用过 wait 方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果 notify 方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在 notfiy 方法后增加一个等待和一些代码,看看效果),调用 wait 方法的线程就会解除 wait 状态和程序可以再次得到锁后继续向下运行。

15、请说出你所知道的线程同步的方法。

wait():使一个线程处于等待状态,并且释放所持有的对象的 lock。 sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉 InterruptedException异常。

notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。

notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

16、什么是线程饿死,什么是活锁?

当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。JavaAPI 中线程活锁可能发生在以下情形:

• 当所有线程在序中执行 Object.wait(0),参数为 0 的 wait 方法。程序将发生

活锁直到在相应的对象上有线程调用 Object.notify() 或者 Object.notifyAll()。

• 当所有线程卡在无限循环中。

17、volatile 变量是什么?volatile 变量和 atomic 变量有什么不同

volatile 则是保证了所修饰的变量的可见。因为 volatile 只是在保证了同一个变量在多线程中的可见性,所以它更多是用于修饰作为开关状态的变量,即 Boolean 类型的变量。

volatile 多用于修饰类似开关类型的变量、Atomic 多用于类似计数器相关的变量、其它多线程并发操作用 synchronized 关键字修饰。

volatile 有两个功用:

• 这个变量不会在多个线程中存在复本,直接从内存读取。

• 这个关键字会禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。

18、volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗?

volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。 在 Java 中除了 long 和 double 之外的所有基本类型的读和赋值,都是原子性操作。而 64 位的 long 和 double 变量由于会被 JVM 当作两个分离的 32 位来进行操作,所以不具有原子性,会产生字撕裂问题。但是当你定义 long 或 double 变量时,如果使用 volatile 关键字,就会获到(简单的赋值与返回操作的)原子性。

19、什么是线程池

线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

20、为什么使用线程池?线程池的作用

创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。

线程池作用就是限制系统中执行线程的数量,1、提高效率 创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的多。2、方便管理 可以编写线程池管理代码对池中的线程同一进行管理,比如说启动时有该程序创建100个线程,每当有请求的时候,就分配一个线程去工作,如果刚好并发有101个请求,那多出的这一个请求可以排队等候,避免因无休止的创建线程导致系统崩溃。

21、几种常见的线程池及使用场景

1、newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

2、newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

3、newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

4、newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。

22、线程池中的几种重要的参数

corePoolSize:就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收

maximumPoolSize:就是线程池中可以容纳的最大线程的数量

keepAliveTime:就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间,

util:就是计算这个时间的一个单位。

workQueu:就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。

threadFactory:就是创建线程的线程工厂。

handle:是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。

23、线程池的工作流程

 

 

 

 

 

 

 

发布了45 篇原创文章 · 获赞 11 · 访问量 4818

猜你喜欢

转载自blog.csdn.net/weixin_44187963/article/details/103513726