栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java 面试之多线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java 面试之多线程

目录
      • 1. 进程和线程的区别?
      • 2. 什么叫线程安全?
      • 3. 线程的几种状态?
      • 4. Java创建多线程后,调用start()和run()方法的区别?
      • 5. 对Future和FutureTask的理解?
      • 6. 怎么解决共享资源竞争,实现线程安全?
      • 7. synchronized,volatile和CAS的区别?
      • 8. 用volatile修饰的变量和用atomic修饰的变量有什么不同?
      • 9. 使用volatile关键字的场景?
      • 10. synchronized和Lock的区别?
      • 11. 对wait(),notify(),和notifyAll()的理解?
      • 12. wait()和sleep()的区别?
      • 13. 对yield(),join()的理解?
      • 14. Java中什么是竞态条件?
      • 15. 定时线程的使用?
      • 16. 线程同步常见方法?
      • 17. 如何停止一个线程?
      • 18. interrupt(),interrupted(),isInterrupted()方法的区别?
      • 19. 线程池的优点?
      • 20. 实现多线程有哪些方式(线程的创建方式)?
      • 21. 什么是乐观锁和悲观锁?
      • 22. 什么叫死锁?
      • 23. 产生死锁的四个必要条件?
      • 24. 导致死锁的原因?
      • 25. 守护(后台)线程和非守护线程的区别?
      • 26. 什么是多线程的上下文切换?
      • 27. 怎么唤醒一个阻塞的线程?
      • 28. 线程池中execute()和submit()方法的区别?
      • 29. 如果提交任务时,线程池队列已满,这时会发生什么?
      • 30. 线程池的类型?
      • 31. 线程池的几个重要参数?
      • 32. 线程池的阻塞队列有哪些?
      • 33. 线程池的拒绝策略有哪些?

1. 进程和线程的区别?
  • 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位,一个进程包含多个线程;
  • 并发性:多个进程可以并发执行,多个线程也可以并发执行;
  • 拥有资源:进程是拥有资源的一个独立单位,线程不拥有资源,但可以访问所属进程的资源;
  • 系统开销:在创建和撤销进程的时候,都要为其分配和释放资源,所以创建和撤销进程的资源要大于创建和撤销线程的资源;
  • 内存空间:进程有独立的地址空间,线程有自己的堆栈和局部变量,但没有独立的地址空间。
2. 什么叫线程安全?

如果代码所在进程中有多个线程在同时运行,并且这些线程会同时运行某一段代码,如果每次运行的结果和单线程运行这段代码的结果一致,而且各个变量的值也和预期的一样,那么这段代码就是线程安全的。

3. 线程的几种状态?
  • 新建状态(New):新创建了一个线程对象;
  • 就绪状态(Runnable):线程处于可运行状态,除了CPU之外,其他的运行所需资源都已全部获得;
  • 运行状态(Running):就绪状态的线程获得了CPU的使用权,程序代码开始执行;
  • 阻塞状态(Blocking):线程因某种原因而放弃CPU,暂时停止运行。阻塞的情况分三种:
    • 等待阻塞:调用了wait()方法,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒;
    • 同步阻塞:运行的线程所需要的对象的同步锁被其他线程占用;
    • 其他阻塞:调用了sleep()或join()方法,或等待某个I/O请求完成,调用yield()方法并不会使线程阻塞,即线程仍处于就绪状态。
  • 死亡状态(Dead):线程执行完毕或因异常退出了run()方法,该线程结束生命周期。
4. Java创建多线程后,调用start()和run()方法的区别?

调用start()方法后会初始化一个线程,然后线程会被放到等待队列,等待CPU调度,这意味着该线程并不会马上执行,start()方法体中会调用到具体任务的run()方法;
而单单调用run()方法的话只是普普通通地调用Runnable对象的一个方法,并没有经历start()方法所做的线程的准备工作,和普通对象调用普通方法一样。

补充:定义任务用Runnable或Callable接口,区别是前者不产生返回值,后者产生返回值,后者通常结合Future和FutureTask使用,用于异步获取结果,调用ExecutorService对象的submit()方法会产生Future对象,可以用isDone()方法来查询Future是否完成,用get()方法来获取结果;定义线程用Thread。

5. 对Future和FutureTask的理解?
  • Future是一个接口,为Runnable或Callable任务提供:①取消②查询是否取消成功③查询是否执行成功④获取执行结果的功能;
  • FutureTask是Future接口的实现类,它既可以作为一个Runnable任务,也可以作为一个Future,它具备Future接口的所有功能,还额外提供了许多其他功能,例如设置结果,处理可能发生的中断等。

Future模式的思想就是在子线程执行的时候,主线程不阻塞仍继续执行,等到主线程需要子线程的执行结果时再去获取子线程的执行结果(此时子线程没有执行完成的话就会阻塞直至完成),主线程还可以根据一定的逻辑对子线程进行操作,例如设置是否需要阻塞等待子线程完成。

6. 怎么解决共享资源竞争,实现线程安全?
  • 使用synchronized关键字修饰方法或代码块:只有获得锁的线程才能访问资源;
  • 使用显示的Lock对象对代码块进行加锁释放锁:只有获得锁的线程才能访问资源;
  • 使用原子类(AtomicInteger,AtomicLong,AtomicReference)来保证原子性:原子操作是不能被线程调度机制中断的操作,一旦开始,那么就会在线程上下文切换之前完成,这样就保证了其他线程来不及修改这个变量,仅适用于简单操作,原子性就是其他线程不能对本线程做出的修改进行干涉,即不能中途插入进来改变变量的值;
  • 使用volatile修饰域:只要某个线程对这个域进行了修改,其他的线程都将看到变化;
  • 使用线程本地存储(ThreadLocal):根除对变量的共享,为使用相同变量的每个不同的线程都创建不同的存储。
7. synchronized,volatile和CAS的区别?
  • synchronized保证可见性和原子性,会导致其他线程阻塞,属于悲观锁;
  • volatile保证可见性,不保证原子性,禁止指令重排,不需要加锁;
  • CAS保证可见性及原子性,不会导致其他线程阻塞,属于乐观锁。

补充:
Java内存模型:Java所有变量都存储在主内存中,所有线程都可以访问;每个线程创建时,JVM会为其创建一个私有的工作内存,里面保存该线程使用到的变量的副本;线程对变量的操作都必须在自己的工作内存中进行,读取时从主内存将变量读取到工作内存中,在工作内存中修改完毕后再刷新到主内存中。
指令重排:是指代码的执行过程有时并不是按照代码书写过程来的,编译器和CPU可以保证在输出结果一致的情况下对代码进行重排序优化,以提高执行效率。
内存屏障:内存屏障是一个CPU指令,可以禁止指令重排,它可以确保屏障前的指令不会排到屏障后面,屏障后的指令不会排到屏障前面,即在内存屏障这条指令执行时,它前面的指令都已执行完毕。
volatile和内存屏障的关系:当用volatile修饰一个变量时,会在对变量进行读操作前插入一个读屏障指令,在对变量进行写操作后插入一个写屏障指令。
volatile为什么不能保证原子性呢:修改volatile变量分为四步,①从主内存中读取变量到工作内存中;②修改变量值;③写回到工作内存;④刷新回主内存。举个例子,一个变量i被volatile修饰,两个线程想对这个变量修改,都对其进行自增操作也就是i++,在字节码及底层,i++被抽象为三个操作:即先取值,再自加,再赋值三个操作,线程A首先得到了i的初始值100,但是还没来得及修改,就阻塞了,这时线程B开始了,它也得到了i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是100,之后对其进行加1操作,得到101后,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。问题来了,线程A已经读取到了i的值为100,也就是说读取的这个原子操作已经结束了,接下来就不会再读取而是直接对变量进行改动了,所以这个可见性来的有点晚,即线程A不知道变量i已经被线程B修改了还在傻乎乎地延续它自己的修改,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存,所以即便是volatile具有可见性,也不能保证对它修饰的变量具有原子性。

8. 用volatile修饰的变量和用atomic修饰的变量有什么不同?

用volatile修饰的变量不具有原子性,用atomic修饰的变量具有原子性。

9. 使用volatile关键字的场景?
  • 对变量的写操作不依赖于当前值,比如自增、自减等操作;
  • 该变量不宜和其他变量位于同一表达式中。

总而言之,就是需要满足原子性操作的前提。

10. synchronized和Lock的区别?
  • synchronized是一个关键字,而Lock是一个接口;
  • synchronized是隐式的加锁,Lock是显式的加锁;
  • synchronized可以作用在方法和代码块上,而Lock只能作用在代码块上;
  • synchronized是阻塞式加锁,而Lock中的tryLock()支持非阻塞式加锁;
  • synchronized没有超时机制,而Lock中的tryLock()可以支持超时机制;
  • synchronized不可中断,而Lock中的lockInterruptibly()可中断的获取锁。(ReentrantLock.lockInterruptibly()允许在等待时由其它线程调用等待线程的Thread.interrupt()方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。ReentrantLock.lock()方法不允许Thread.interrupt()中断,使检测到Thread.isInterrupted()一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程);
  • synchronized采用的是monitor对象监视器,Lock的底层原理是AQS;
  • synchronized只有一个同步队列和一个等待队列,而Lock有一个同步队列,可以有多个等待队列(同步队列:排队取锁的线程所在的队列;等待队列:调用wait()方法后,线程会从同步队列转移到等待队列);
  • synchronized是非公平锁,而Lock可以是公平锁也可以是非公平锁;
  • synchronized用notify()方法进行唤醒,而Lock用condition唤醒;
  • Lock有ReadWriteLock支持并发读。
11. 对wait(),notify(),和notifyAll()的理解?
  • wait()会在等待外部事件产生变化的时候将任务挂起,并且只有在notify()或notifyAll()发生时,即发生了某些令人感兴趣的事件时,这个任务才会被唤醒并重新执行;
  • wait()方法释放锁,notify()或notifyAll()方法加锁;
  • 只能在同步控制方法或同步代码块(即用synchronized修饰)里调用wait(),notify()或notifyAll()方法;
  • 在调用wait(),notify()或notifyAll()方法前必须先获得对象的锁;
  • notify()唤醒单个线程,但是不能指定具体条件,所以只有一个线程在等待时(即只有一个线程调用了wait()方法)才适用,notifyAll()唤醒所有等待线程(即所有调用了wait()方法的线程)。
12. wait()和sleep()的区别?
  • 调用sleep()方法会导致此线程暂停执行指定时间,将CPU时间片让给其他线程,但是它不会释放已获得的对象锁;
  • 调用wait()方法会释放对象锁,只有针对此对象调用notify()或notifyAll()方法后线程才会继续执行。
13. 对yield(),join()的理解?
  • yield()方法用于暂停正在执行的线程,将CPU的控制权转交给其他线程,本线程进入就绪状态(注意不是阻塞状态),意在合理控制线程的执行流程;
  • join()方法可理解为插队,当一个线程需要等待另一个线程的返回结果时,就需要用到该方法,调用该方法的线程的优先级达到最高,其他线程要等待该线程执行完毕。
14. Java中什么是竞态条件?

当多个线程访问同一资源时,如果对线程访问资源的顺序有要求,就称存在竞态条件。

15. 定时线程的使用?
  • 使用定时器Timer类;
  • 使用定时调度线程池ScheduledExecutorService。
16. 线程同步常见方法?

sleep(),wait(),notify(),notifyAll(),await(),signal(),signalAll()。

17. 如何停止一个线程?
  • run()方法或call()方法执行完后线程自动停止;
  • 使用interrupt()方法中断线程。
18. interrupt(),interrupted(),isInterrupted()方法的区别?
  • interrupt()方法用于中断线程,但是仅仅是将线程的中断状态设置为ture,线程是否真的中断需要线程自己时不时的判断这个状态来决定。具体地说,当一个线程调用interrupt()方法时,①如果线程处于阻塞状态(sleep(),wait(),join()等),那么线程将立即退出阻塞状态,并抛出一个InterruptedException异常,仅此而已,中断状态会复位为false;②如果线程处于运行状态或者I/O异常状态或者其他状态,那么中断状态将置为true,仅此而已,不会真的中断线程。也就是说,线程检查到中断标志位为true时,就自行停止线程,处于阻塞状态的线程在检测到InterruptedException异常后,就需要在catch中停止线程。
  • interrupted()方法用于返回当前线程的中断状态,并且会清除中断标志(即中断标志复位为false),也就是说,如果一个被中断线程(中断标志为true)调用了该方法,那么返回的结果是true,但是当前线程的中断状态标志被清除为false了,下一次再调用该方法返回的状态结果就是false了。
  • isInterrupted()方法用于返回调用该方法的线程对象所对应的线程的中断状态,但是它不会修改中断标志。interrupted()方法和isInterrupted()方法都是用来返回线程的中断状态的,区别就在于作用的线程对象不同,还有是否要清除中断标志状态位。

补充:当抛出InterruptedException异常或者任务调用了interrupted()方法时,中断状态将被复位,其他的都不会修改中断状态。

19. 线程池的优点?
  • 重用存在的线程,减少开销;
  • 可有效地控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免阻塞;
  • 可提供定时执行,并发数控制等功能。
20. 实现多线程有哪些方式(线程的创建方式)?
  • 实现Runnable接口,重写run()方法;
  • 实现Callable接口,重写call()方法;
  • 继承Thread类,重写run()方法,Thread实现了Runnable接口,因此含有run()方法;
  • 使用线程池。
21. 什么是乐观锁和悲观锁?
  • 乐观锁:就像它的名字一样,对并发间操作产生的线程安全问题持乐观状态,它认为竞争不总是会发生,因此它不需要持有锁,将比较-替换两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,则进行相应的重试机制;
  • 悲观锁:还是像它的名字一样,对并发间操作产生的线程安全问题持悲观状态,它认为竞争总是会发生,因此每次对某资源进行操作时,总是会对资源进行加锁,除非它释放锁,不然别的线程只有一直等待。
22. 什么叫死锁?

某个线程在等待另一个线程,而后者又等待别的线程,这样一直下去,直到这个链条上的线程又在等待第一个线程释放锁,这就造成了各个线程之间相互等待无法释放锁,导致线程无法继续执行下去,这被称为死锁。

23. 产生死锁的四个必要条件?
  • 互斥条件:线程使用的资源中至少有一个是不能共享的;
  • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放;
  • 不剥夺条件:资源不能被线程强行抢占;
  • 循环等待条件:若干线程之间形成一种头尾相连的循环等待资源关系。

同时满足以上四个条件时,就会产生死锁。

24. 导致死锁的原因?
  • 资源竞争不当
  • 线程获取锁的顺序不当
25. 守护(后台)线程和非守护线程的区别?

守护线程是指程序运行的时候在后台提供一种通用服务的线程。当所有的非守护线程结束时,程序也就终止了,也就是JVM停止运行了,同时会杀死进程中的所有守护线程,换句话说,只要有任何非守护线程还在运行,程序就不会终止,守护线程也不会被干掉。守护线程的一个例子就是垃圾回收器,只要JVM不停止,垃圾收集器就会一直运行下去。

26. 什么是多线程的上下文切换?

多线程的上下文切换是指CPU的控制权由一个正在运行的线程切换到另一个处于就绪状态并等待CPU执行权的线程的过程。

27. 怎么唤醒一个阻塞的线程?
  • sleep()或join()导致的阻塞:等待时间完成或上一个线程执行完成;
  • wait():使用notify()或notifyAll()。
28. 线程池中execute()和submit()方法的区别?
  • execute()是Executor接口中的方法,submit()是ExecutorService接口中的方法,ExecutorService接口继承了Executor接口;
  • execute()只能接受Runnable任务参数,submit()既可以接受Runnable任务参数也可以接受Callable任务参数;
  • execute()没有返回值,submit()返回Future类型对象。
29. 如果提交任务时,线程池队列已满,这时会发生什么?

非定长的队列会先扩容,扩容到不能再扩容了就调用拒绝策略,定长的线程池队列会直接调用拒绝策略。

30. 线程池的类型?


补充:7.默认线程池修改
ThreadPool threadPoolExecutor = new ThreadPoolExecutor(参数);

31. 线程池的几个重要参数?

32. 线程池的阻塞队列有哪些?
线程池类型队列类型备注
newCachedThreadPoolSynchronousQueue同步队列,无缓冲,拿一个出来,放一个进去,例如生产者-消费者模式
newFixedThreadPoolLinkedBlockingQueue
newScheduledThreadPoolDelayedWorkQueue
newSingleThreadExecutorLinkedBlockingQueue
newSingleThreadScheduledExecutorDelayedWorkQueue
newWorkStealingPool可使用多种队列,每个线程对应一种队列
ThreadPoolExecutor自行通过参数指定默认
33. 线程池的拒绝策略有哪些?

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/981561.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号