Java口试题——第四篇(多线程)

[复制链接]
发表于 2026-2-9 01:03:56 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
1. sleep(0)的意义

他的意义在于 调用Thread.sleep(0)的当火线程确实的被冻结了一下,让其他线程偶然机优先实行。相当于一个让位动作。
在线程没退出之前,线程有三个状态:停当态、运行态、等待态。sleep(n) 之以是在n秒内不会到场CPU竞争,是由于,当线程调用sleep(n)的时间,线程是由运行态转入等待态,线程被放入等待队列中,等待定时器n秒后的制止变乱,当到达n秒计时后,线程才重新由等待态转入停当态,被放入停当队列中,等待队列中的线程是不到场cpu竞争的。只有停当队列中的线程才会到场cpu竞争,所谓cpu调治,就是根据肯定的算法,从停当队列中选择一个线程来分配cpu变乱。
sleep(0) 之以是立刻归去到场cpu竞争,是由于调用sleep(0)后,由于0的缘故原由,线程直接回到停当队列,到场cpu竞争。
2. synchronized原理及应用

synchronized可以包管方法大概代码块在运行时,同一时间只有一个方法可以进入到临界区,同时还可以包管共享变量的内存可见性。
Java中每一个对象都可以作为锁,这是sychronized实现同步的底子。

  • 平凡同步方法:锁的是当前实例对象
  • 静态同步方法:锁的是当前类的class对象
  • 同步方法块:锁的是括号内里的对象。
同步方法块时使用monitorenter和monitorexit指令来实现,而对于同步方法使用acc_sychronized来完成。无论采取哪种方式,本质都是对一个对象的监视器举行获取,而这个获取过程是排他的,也就是同一时间只能有一个线程获取到由synchronized所掩护对象的监视器。任何一个对象都有本身的监视器,实行方法的线程必须先获取到该对象的监视器才华进入方法块和同步方法,而没有获取到监视器的线程将会被壅闭。
Java对象头和monitor是实现synchronized的底子,Java对象头中重要包罗两部门数据,标记字段和范例指针。标记字段用于存储对象自身的运行时数据,包罗哈希码、GC分代年事、锁状态标记、方向线程ID等。假造机通过范例指针来确定对象是哪个类的实例。
锁的重要存在四种状态,依次是:无锁、方向锁、轻量级锁、重量级锁。他们会随着竞争的猛烈而徐徐升级,注意锁可以升级不可以降级,这种计谋是为了进步得到锁和开释锁的服从。
3. 用过哪些原子类,他们的原理是什么

Atomic包下的类,根本都是使用Unsafe实现的包装类,在底层就是CAS操纵实现的。
4. 线程池的关闭方式有几种,各自的区别是什么

可以调用线程池的shutdown大概shutdownNow方法来关闭线程池。他们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来制止线程,以是 无法相应制止的使命大概永久无法制止。
5. Spring的Controller是单例照旧多例,怎么包管并发的安全

Spring中的Controller默认是单例的,也就是singleton模式。以是如果Controller中有一个私有变量a,全部哀求到同一个Controller时,使用的变量a都是公用的,即如果某个哀求修改了这个变量a,则在别的哀求中可以或许读到这个修改的内容。
为了包管并发安全,常见有两种办理方法

  • 在Controller中使用ThreadLocal变量
  • 在Spring设置文件Controller中声明为scope=“prototype”,每次都创建新的controller,不再使用单例模式。
6. 怎样实现一个并发安全的链表


  • 采取粗粒度锁,完全锁住链表。
  • 采取细粒度锁,只锁住必要修改的节点。
  • 使用CAS来修改节点。
7. 有哪些无锁的数据布局,怎么做

要实现一个线程安全的队列有两种方式:壅闭和非壅闭。壅闭队列就是锁的应用如sychronized、ReentrantLock。而无壅闭就是CAS算法的应用(无锁),比力常见的是ConcurrentLinkedQueue,这是一个基于链表节点的无边界的线程安全队列,采取FIFO原则对元素举行排序,采取CAS算法实现。
8. 对AbstractQueuedSychronizer的相识,其加锁以及解锁流程,独占锁和公平锁加锁有什么差异


  • AQS即队列同步器,是用来构建锁大概其他同步组件的底子框架,使用了一个int成员变量表现同步状态,通过内置的FIFO队列来完成资源获取线程的列队工作。
  • AQS使用一个int成员变量state来表现同步状态,当state>0时表现已经获取了锁,当state=0时表现开释了锁。他提供了三个方法getState()、setState(int newState)、compareAndSetState(int expect,int update)来对同步状态state举行操纵,固然AQS对state的操纵是安全的。
  • AQS通过内置的FIFO同队伍列来完成线程获取资源的列队工作,如果当火线程获取同步状态失败时,AQS会将当火线程以及等待状态等信息构造成一个节点(Node)并将其参加队列,同时会壅闭当火线程,当同步状态开释时,则会把节点中的线程叫醒,使其在次实验获取同步状态。
共享式获取和独占式获取最重要的区别在于同一时间可否有多个线程同时获取到同步状态。读写锁ReentrantReadWriteLock,他的读取锁ReadLock是共享式的,但是他的WriteLock就是独占式的。
公平锁和非公平锁,公平的获取锁也就是等待时间最长的线程最优先得到锁。其关键在于获取锁的时间是否按照FIFO的序次来。
9. ConcurrentLinkedQueue和LinkedBlockingQueue的用处和差异之处

ConcurrentLinkedQueue采取CAS操纵Node节点(Node里的元素也使用volatile修饰)来包管元素的同等性。LinkedBlockingQueue使用一个独占锁来包管线程安全,然后用Condition来做壅闭操纵。实现了先辈先出特性,是作为生产者消耗者的首选。
10. 谈谈对读写锁的明白


  • 和排他锁ReentrantLock差异,读写锁ReentrantReadWriteLock在同一时间可以允许多个读线程访问,但是在写线程访问时,全部的读线程和其他写线程均被壅闭。读写锁维护了一个对锁,一个读锁和一个写锁,通太过离读锁和写锁,使得并发性相比一样寻常的排他锁有了很大的提拔。
  • 写锁是一个支持可重入的排他锁,写锁的状态获取终极会调用tryAcquire(int arg)方法,在判定重入时参加了一项条件:读锁是否存在,由于要确保写锁的操尴尬刁难读锁是可见的,因此只有等读锁完全开释后,写锁才华够被当火线程所获取。一旦写锁获取了,全部其他读、写线程均会被壅闭。
  • 读锁是一个可重入的共享锁,他可以或许被多个线程同时获取,在没有其他写线程访问时,读锁总是获取乐成。
11. 可以创建volatile数组吗

Java中可以创建volatile数组,不外只是一个指向数组的引用,而不是整个数组。如果改变引用指向的数组,将会收到volatile掩护,但是,如果多个线程同时改变数组的元素,volatile标识符就不能起到掩护作用。
同理,对于Pojo类,使用volatile修饰,只能包管这个引用的可见性,不能包管其内部的属性。
12.  一个线程池筹划的最大线程数应该思量哪些因素

要想公道的设置线程池的巨细,起主要分析使命的特性,可以从以下几个角度分析

  • 使命的性子:盘算麋集型使命、IO麋集型使命
  • 使命的优先级:高、中、低
  • 使命的实行时间:长、中、短
  • 使命的依赖性:是否依赖其他体系资源,如数据库操纵。

  • 在有N个CPU的体系上,盘算麋集型使命应设置尽大概少的线程,可以将线程池巨细设置为N+1.
  • IO麋集型使命应该设置尽大概多的线程,由于IO操纵不占用CPU,应加大线程数量,如线程巨细设置为2N+1.
  • 结论:线程等待时间所占比例越高,必要越多线程。线程CPU时间所占比例越高,必要越少线程。
13. 为什么线程池中线程等待时间所占比例越高,必要越多线程,CPU所占比例越高,必要越少线程

1. 线程等待时间所占比例高,必要越多线程

  • 淘汰等待延长:当线程实行的使命包罗大量的等待时间(如IO操纵、数据库查询、网络通讯等),单个线程在实行过程中会有大量时间处于非CPU实行状态。这种环境下,增长线程数量可以让更多的线程在等待时同时实行,从而淘汰因等待导致的延长和资源闲置。
  • 进步体系吞吐量:在多线程环境下,每个线程都大概在等待外部资源时停息实行,通过增长线程数量,纵然每个线程都在等待,也能有更多的线程在处理处罚差异的使命,从而增长体系的团体吞吐量。
  • 并行实行上风:在IO麋集型应用中,并行实行多个线程可以淘汰总体实行时间,由于多个IO操纵可以同时举行,不会相互壅闭。
2. 线程CPU时间所占比例高,必要越少线程

  • 制止上下文切换开销:当线程重要举行CPU麋集型使命时,增长线程数量会增长CPU上下文切换的频率。上下文切换是CPU从一个线程切换到另一个线程时生存和规复状态的过程,这一过程必要斲丧肯定的时间。
  • 进步CPU使用率:在CPU麋集型使命中,每个线程都能有用使用CPU资源。此时,较少的线程就能保持较高的CPU使用率,无需引入额外的线程来淘汰等待时间。
  • 简化体系复杂性:过多的线程在CPU麋集型使命中不但不会带来性能提拔,反而会增长体系的复杂性和管理难度。
14. Java中用到的线程调治算法是什么

分时调治模子和抢占式调治模子

  • 分时调治模子:指让全部的线程轮番得到cpu的使用权,而且均匀分配每个线程占用的cpu的时间片。
  • 抢占式调治模子:优先让可运行池中优先级高的线程占用cpu,如果可运行池中的线程优先级雷同,就随机选择一个线程,使其占用cpu。
15. 并发编程的三个告急特性

并发编程的三个告急特性分别为:原子性、可见性、有序性。
16. 多线程中i++线程安全吗

不安全。i++不是原子性操纵,i++分为读取i值,对i值加1,再赋值给i++。实行期中任何一步都有大概被其他线程抢占。
17. Java中创建线程有哪几种方式


  • 继承Thread类创建线程
  • 通过Runnable接口创建线程
  • 通过Callable和Future创建线程

18. Runnable和Callable有什么区别

Runnable接口不会返回值大概抛出查抄非常,但是Callable接口可以。
19. 线程类的构造方法、静态块是被哪个线程调用的

假设Thread2中new了Thread1、main函数中new了Thread2.

  • Thread2的构造方法、静态块是被main函数调用的,Thread2中的run方法是线程本身调用的。
  • Thread1的构造方法、静态块是被Thread2调用的,Thread1中的run方法是线程本身调用的。
20. 怎样制止一个正在运行的线程


  • 使用退出标记,使线程正常退出,也就是当run方法完成后线程制止。
  • 使用stop方法强行制止,但是不保举这个方法,由于stop和suspend、resume一样都是逾期的方法。
  • 使用interrupt方法制止线程。
21. stop和suspend方法为何不保举使用

在Java中,stop和suspend方法是Thread类的两个方法,但是都不保举使用,重要缘故原由是他们大概导致严峻的安全题目和死锁题目。

  • stop方法不保举使用缘故原由


  • 不安全性:stop方法用于立即制止一个线程,但这会导致线程突然制止而无法完成清算工作。当线程被陵暴制止时,他大概正在实行关键使命。
  • 粉碎对象状态:制止线程会使它解锁它已锁定的全部监视器。如果先前有这些监视器掩护的任何对象处于不同等状态,则其他线程大概会以不同等状态检察这些对象,这大概导致步调出现运动非常。
  • 无法规复:一旦线程被stop()方法制止。他就不能被重新启动大概规复实行。

  • suspend方法不保举使用缘故原由


  • 死锁风险:suspend方法用于停息线程的实行,但他不会开释线程所持有的锁。如果一个线程在实行suspend时持有某个锁,而另一个线程在等待这个锁以继承实行,那么这两个线程大概会陷入思考状态,由于被停息的线程不会开释锁,而等待的线程又无法继承实行。

  • 使用interrupt方法:interrupt()方法用于制止线程,但他是安全的,由于他不会立即制止线程的实行,而是向线程发送一个制止信号。线程可以通过查抄其制止状态来相应制止,并有序的清算资源和退出。
22. 谈谈对线程制止的明白

在Java中以为,一个线程不应该由其他线程来陵暴制止大概制止,以是一些会陵暴制止线程的方法如Thread.stop()、Thread.suspend()方法都已经被废弃。一样寻常是通过调用thread.interrupt()方法来设置线程的制止标识。

  • 如果线程是处于壅闭状态、会抛出InterruptedException非常,代码可以举行捕获,举行一些处理处罚,比方Object.wait()、Thread.sleep()、BlockingQueue.put()、BlockingQueue.take()等。
  • 如果线程是处于Runnable状态、也就是正常运行,调用thread.interrupt()只是会设置制止标识位,不会有什么其他操纵。
23. 线程sleep()和yield()方法有什么区别


  • sleep()方法给其他线程运行机遇时不思量线程的优先级,因此会给低优先级的线程以运行的机遇,yield()方法只会给雷同优先级大概更高优先级的线程以运行的机遇。
  • 线程实行sleep()进入壅闭状态,而实行yield方法后转入停当状态。
  • sleep()方法比yield()方法具有更好的可移植性。
24. 死锁和活锁的区别

死锁:指两个大概两个以上的线程在实行过程中,因争取资源而导致的一种相互等待的征象,若无外力作用,他们都将无法推进下去。
活锁:使命大概实行者没有被壅闭、由于某些条件没有满足,导致不绝重复实验、失败、实验、失败。
25. 什么是无锁

无锁,即没有对资源举行锁定,即全部的线程都能访问并修改同一个资源,但同时只有一个线程能修改乐成。无锁典范的特点是一个修改操纵在一个循环内举行,线程会不绝的实验修改共享资源。如果没有辩论就修改乐成并退出否则就会继承下一次循环实验。以是,如果有多个线程修改同一个值肯定会有一个线程能修改乐成。而其他修改失败的线程会不绝实验直到修改乐成。
26. 乐观锁有什么缺点


  • ABA题目
    如果一个变量V初次读取的时间是A值,而且在预备赋值的时间查抄到它仍旧是A值,那么我们就能分析它没有被其他线程修改过吗?很显着是不大概的。由于在这段时间内大概被修改为其他值,然后又改回A,那么CAS操纵就会误以为它从来没有被修改过。
  • 循环时间长开销大
    自旋CAS如果长时间不乐成,会给CPU带来非常大的实行开销。如果JVM能支持处理处罚器提供的pause指令那么服从就会有所提拔。
  • 只能包管一个共享变量的原子性操纵
    CAS只对单个共享变量有用,当操纵涉及多个共享变量时CAS无效,但是从JDK1.5开始,提供了AtomicReference类来包管引用对象之间的原子性。
27. 多线程锁的升级原理是什么

在Java中,锁共有4种状态,级别从低到高分别为:无状态锁、方向锁、轻量级锁、重量级锁。

  • synchronized锁升级原理:在锁对象的对象头里有一个threadid字段,在第一次访问时间threadid为空,jvm会让其持有方向锁,并将threadid设置为其线程id。再次进入的时间会判定threadid是否与其线程id同等。如果同等,则可以直接使用此对象,如果不同等,则升级方向锁为轻量级锁,通过自旋循环肯定次数来获取此锁,实行肯定次数后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了synchronized锁升级。
  • 锁升级目的:锁升级是为了低沉锁带来的性能斲丧。
28. wait方法和sleep方法的区别


  • sleep是Thread类的静态方法、wait来自Object类。
  • 最重要的是 sleep方法没有开释锁,而wait方法开释了锁。使得其他线程可以使用同步控制块大概方法。
  • wait、notify、notifyAll只能在同步控制方法大概同步控制块内里使用,而sleep可以在任何地方使用。
  • sleep必须捕获非常,wait、notify等不必要。
29. 为什么wait()、notify()、notifyAll()必须在同步方法大概代码块中被调用

当一个线程必要调用对象的wait方法时,这个线程必须拥有该对象的锁 ,接着他就会开释这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程必要调用对象的notify()方法时,他会开释这个对象的锁。以便其他在等待的线程就可以得到这个对象锁。由于全部的这些方法都必要线程持有对象的锁,就只能通过同步来实现。
30. JUC包中的原子类是哪4类


  • 根本范例


  • AtomicInteger
  • AtomicLong
  • AtomicBoolean

  • 数组范例


  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

  • 引用范例


  • AtomicReference
  • AtomicStampedReference

  • 对象的属性修改范例


  • AtomicIntegerFiledUpdater
  • AtomicLongFieldUpdater
31. 线程池的状态

线程池有5种状态,Running、ShutDown、Stop、Tidying、Terminated

  • Running(运行状态): 正常状态,吸取新的使命,处理处罚等待队列中的使命。
  • ShutDown (关闭状态) : 不担当新的使命提交,但是会继承处理处罚等待队列中的使命。
  • Stop(制止状态):不担当新的使命,不再处理处罚等待队列中的使命,制止正在实利用命的线程。
  • Tidying(整理状态): 当线程池中全部使命都已经制止,线程数为0时,线程池就会进入Tidying状态。在这个过程中,线程会实行terminated钩子方法,terminated方法在ThreadPoolExecutor类中是空的,但用户可以在子类中重载该方法以实现特定的清算逻辑。
  • terminated(制止状态):当terminated()方法实行竣事后,线程池就会进入terminated状态,表现线程池已经彻底关闭,且不能再重新启动。
32. 线程池ThreadpoolExecutor拒绝计谋有哪些


  • AbortPolicy:抛出非常,拒绝新使命
  • CallerRunsPolicy:调用实行本身的线程运利用命,这种计谋会低沉对于新使命提交的速率,影响步调团体性能。
  • DiscardPolicy:不处理处罚新使命,直接扬弃
  • DiscardOldestPolicy:扬弃最早的未处理处罚新使命。
33. 高并发、使命实行时间短的业务怎样使用线程池

线程数设置为N+1,制止线程上下文的切换。
34. 并发不高、使命实行时间长的业务怎样使用线程池


  • 如果使命实行时间长会集在IO操纵上,增长线程池中线程数量,低沉均匀等待时间。
  • 如果业务时间长会集在盘算操纵上,可以将线程池中的线程数设置的少一点,淘汰线程上下文切换。
35. ThreadLocal造成内存走漏的缘故原由?

ThreadLocalMap中使用的key为ThreadLocal弱引用,而value是强引用。以是,如果ThreadLocal没有被外部强引用的环境下,在垃圾接纳的时间,key会被清算掉。value不会被清算掉。 如许一来,ThreadLocalMap就会出现key为null的Entry。ThreadLocalMap已经思量了这种环境,在调用set()、get()、remove()时,会清算掉key为null的纪录。使用完ThreadLocal方法后,最妙手动调用remove方法
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表