面试-多线程常考

1、进程与线程的区别

  • 进程:在内存中分配自己独立的运行空间,彼此之间不会相互影响。这种独立的应用空间称为进程。也可以通用的理解为进程就是一个运行的程序
  • 线程:位于进程中,负责当前进程中某个具备独立运行资格的空间。一个进程中至少有一个线程。

2、引入多线程目的

  • 更加合理的利用计算机资源
  • 提高程序运行效率

3、synchronized的缺点(一般与Lock做比较)

  • synchronized是java的关键字,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行了代码块时,其他线程只能一直等待,直到该线程释放锁
  • 无法实现读写锁分离,如果一个线程获取锁,其他线程将无法再次获取

3、Lock与synchronized的区别

  • Lock是java语言申明的接口,由jdk1.5引入,synchronize是java关键字
  • Lock需要手动释放锁,而synchronized不需要手动释放,代码执行完毕或者抛出异常,jvm会要求线程主动释放锁
  • Lock支持更多的获取锁机制,如可以设置等待时长(tryLock(longtime,unit)),或者非阻塞获取锁(tryLock)或者可设置中断(lockinterruptibly)
  • 通过Lock可以轻松实现读写锁分离
  • synchronized下如果一个线程处于等待某个锁状态,是无法被中断的
  • 从性能角度,如果竞争不激烈,两者性能差不多,而当竞争激烈时,Lock的性能要远远优于synchronized
    特别注意
    1. 使用Lock接口时,如果有异常发生,jvm不会释放锁,这样会造成死锁,最佳实践是在finally中释放锁
    2. 可中断是指等待线程,并不是正在执行的线程

读写锁

读写锁主要是对读操作与写操作获取不同的锁,当进行写操作时,需要获取写锁,其他线程将进行等待,直到写操作结束,释放锁。读操作时先获取读锁,此时写操作(写锁)将无法获取,必须等待当前线程释放锁,但是其他读操作仍然可以获取读锁,执行后续逻辑。
- 如果一个线程占用了读锁,此时其他线程需要申请写锁,则该线程进入等待,直到读锁释放
- 如果一个线程占用了写锁,测试其他线程都将等待锁的释放,不论获取读锁还是写锁。

java.util.concurrent包下线程池种类及其特性

  1. Single Thread Executor:只有一个线程的线程池,所有提交的任务都是顺序执行的
  2. Cached Thread Pool:如果线程池有空闲的线程,则使用空闲线程去执行新加入任务,否则将创建新的线程执行新建任务。如果任务执行完毕,没有新任务进入,线程池中线程超过60秒等待时间后,线程将被从线程池删除。
  3. Fixed Thread Pool:拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待。如果任务集中进入,线程不能及时处理,会放入等待队列。
  4. Scheduled Thread Pool:用来调度即将执行任务的线程池,可以设置多长时间执行一次
  5. Single Thread Scheduled Pool:只有一个线程的线程池,用来调度任务在指定时间执行。

Runnable与Callable区别

  • Runnable任务结束后,Future对象返回null,Callable返回的Future实例可以包括返回值
  • Runnable通过execute提交任务,Callable通过submit提交任务
  • Runnable中的run方法无返回值,Callable的call方法具有返回值,通过Future会获取,但是如果要获取返回值,则会进入阻塞状态,直到线程结束。

阻塞消息队列(BlockingQueue)入队方式对比

  • add(Object):如果队列可容纳该对象压入,否则抛出异常
  • offer(Object):如果可能,将对象放入队列,并返回true。如果队列已满,则返回false
  • put(time):如果可能,将对象放入队列。如果队列已满,则阻塞当前线程,直到队列有空间放入队列后返回。

ArrayBlockingQueue与LinkedBlockingQueue比较

  • ArrayBlockingQueue是由数组支持的有界阻塞队列,其队列大小是在构造时确定的,放入该队列的对象是以fifo排序的。
  • LinkedBlockingQueue:无固定大小,构造时可设置队列容量,如果构造时未设置大小,则默认为Integer.MAX_VALUE
  • LinkedBlockingQueue主要使用put和take,所以在消息队列容量已满进行入队或者容量为空获取数据时会阻塞
  • 由于底层使用数据结构不同,LinkedBlockingQueue拥有比较大的吞吐量,但是如果线程数很大时性能要低于ArrayBlockingQueue

为什么使用线程池

涉及到多线程开发,如果并发量较小、或者业务执行频率比较分散,使用new Thread().start()模式是没有问题的。但是如果请求量比较集中,而且并发比较集中、数量较高。如:每一个线程执行1秒,并发tps是10000,即在1秒内,需要创建10000个线程。这会给jvm带来很大压力。此时引入线程池是必要的。具体原因涉及到以下几个方面
- 新建线程的开销,对于jvm新建与销毁线程消耗资源代价比较大,使用线程池可以重复利用现有线程,不需要为每个请求新建线程处理
- 资源消耗量:对每一个请求都新建一个线程,而每个线程是有一定资源消耗的,如果新建线程过多,对jvm运行会带来一定风险。线程池线程可重复利用,而且一定时间空闲后,可自动回收。
- 稳定性:每个jvm或者操作系统对线程数、资源(CPU、内存)有限制,如果无限制的创建线程,可能导致系统异常。

阅读更多
版权声明:知识是公共的,有建议大家一起讨论 https://blog.csdn.net/wanghao_0206/article/details/79947146
文章标签: java 面试 多线程
个人分类: 面试相关
上一篇js书写技巧-jsvascrpt数组
下一篇spring 基于javabean的配置引入外部properties
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭