肝了一夜的66道并发多线程面试题,你不来个666吗?,2024年最新目前最全的《java面试题及解析》是什么

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

ConcurrentHashMap 采⽤分段锁技术,同步容器中,是⼀个容器⼀个锁,但在ConcurrentHashMap中,会将hash表的数组部分分成若⼲段,每段维护⼀个锁,以达到⾼效的并发访问;

13 什么是多线程的上下⽂切换?

1、多线程:是指从软件或者硬件上实现多个线程的并发技术。

2、多线程的好处:

使⽤多线程可以把程序中占据时间⻓的任务放到后台去处理,如图⽚、视屏的下载 发挥多核处理器的优势,并发执⾏让系统运⾏的更快、更流畅,⽤户体验更好

3、多线程的缺点:

⼤量的线程降低代码的可读性;更多的线程需要更多的内存空间, 当多个线程对同⼀个资源出现争夺时候要注意线程安全的问题。

4、多线程的上下⽂切换:

CPU通过时间⽚分配算法来循环执⾏任务,当前任务执⾏⼀个时间⽚后会切换到下⼀个任务。但是,在切换前会保存上⼀个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态。

14 ThreadLocal的设计理念与作⽤?

Java中的ThreadLocal类允许我们创建只能被同⼀个线程读写的变量。因此,如果⼀段代码含有⼀个ThreadLocal变量的引⽤,即使两个线程同时执⾏这段代码,它们也⽆法访问到对⽅的ThreadLocal变量。

概念:线程局部变量。在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同⼀个变量,显然是不⾏的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有⼀种情况之下,我们需要满⾜这样⼀个条件:变量是同⼀个,但是每个线程都使⽤同⼀个初始值,也就是使⽤同⼀个变量的⼀个新的副本。这种情况之下ThreadLocal就⾮常适⽤,⽐如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是⼀个线程安全的变量。⽽我们每个线程都需要使⽤他,并且各自使⽤各自的。这种情况,ThreadLocal就⽐较好的解决了这个问题。

原理:从本质来讲,就是每个线程都维护了⼀个map,⽽这个map的key就threadLocal,⽽值就是我们set的那个值,每次线程在get的时候,都从自⼰的变量中取值,既然从自⼰的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发⽣变化,他仅仅是充当⼀个key的⻆⾊,另外提供给每⼀个线程⼀个初始值。

实现机制:每个Thread对象内部都维护了⼀个ThreadLocalMap这样⼀个ThreadLocal的Map,可以存放若⼲个 ThreadLocal。

15 ThreadPool(线程池)⽤法与优势?

ThreadPool 优点:

减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务 可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为因为消耗过多的内存,⽽把服务器累趴下(每个线程需要⼤约1MB内存,线程开的越多,消耗的内存也就越⼤,最后死机)

------减少在创建和销毁线程上所花的时间以及系统资源的开销

------如不使⽤线程池,有可能造成系统创建⼤量线程⽽导致消耗完系统内存

Java⾥⾯线程池的顶级接⼜是Executor,但是严格意义上讲Executor并不是⼀个线程池,⽽只是⼀个执⾏线程的⼯具。真正的线程池接⼜是ExecutorService。

  1. 当线程数⼩于corePoolSize时,创建线程执⾏任务。

  2. 当线程数⼤于等于corePoolSize并且workQueue没有满时,放⼊workQueue中

  3. 线程数⼤于等于corePoolSize并且当workQueue满时,新任务新建线程运⾏,线程总数要⼩于maximumPoolSize

  4. 当线程总数等于maximumPoolSize并且workQueue满了的时候执⾏handler的rejectedExecution。也就是拒绝策略。

16 Concurrent包⾥的其他东⻄:ArrayBlockingQueue、CountDownLatch等等。

1、ArrayBlockingQueue 数组结构组成的有界阻塞队列。

2、CountDownLatch 允许⼀个或多个线程等待其他线程完成操作;join⽤于让当前执⾏线程等待join线程执⾏结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远wait。

17 synchronized和ReentrantLock的区别?

基础知识:

  1. 可重⼊锁。可重⼊锁是指同⼀个线程可以多次获取同⼀把锁。ReentrantLocksynchronized都是可重⼊锁。

  2. 可中断锁。可中断锁是指线程尝试获取锁的过程中,是否可以响应中断。synchronized是不可中断锁,⽽ReentrantLock则提供了中断功能。公平锁与⾮公平锁。公平锁是指多个线程同时尝试获取同⼀把锁时,获取锁的顺序按照线程达到的顺序,⽽⾮公平锁则允许线程“插队”。synchronized是⾮公平锁,⽽ReentrantLock的默认实现是⾮公平锁,但是也可以设置为公平锁。

  3. CAS操作(CompareAndSwap)。CAS操作简单的说就是⽐较并交换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。⽆论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

  4. Synchronized: isynchronized是java内置的关键字,它提供了⼀种独占的加锁⽅式。synchronized的获取和释放锁由JVM实现,⽤户不需要显示的释放锁,⾮常⽅便。然⽽synchronized也有⼀定的局限性:

当线程尝试获取锁的时候,如果获取不到锁会⼀直阻塞。

如果获取锁的线程进⼊休眠或者阻塞,除⾮当前线程异常,否则其他线程尝试获取锁必须⼀直等待。

  1. ReentrantLock:

ReentrantLock它是JDK 1.5之后提供的API层⾯的互斥锁,需要lock()和unlock()⽅法配合try/finally语句块来完成。

等待可中断避免,出现死锁的情况(如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false)

公平锁与⾮公平锁多个线程等待同⼀个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁⾮公平锁,ReentrantLock默认的构造函数是创建的⾮公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。

18 Semaphore有什么作⽤?

Semaphore就是⼀个信号量,它的作⽤是限制某段代码块的并发数

19 Java Concurrency API中的Lock接⼝(Lock interface)是什么?对⽐同步它有什么优势?

1、Lock接⼝⽐同步⽅法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以⽀持多个相关类的条件对象。

2、它的优势有:

可以使锁更公平 可以使线程在等待锁的时候响应中断 可以让线程尝试获取锁,并在⽆法获取锁的时候⽴即返回或者等待⼀段时间 可以在不同的范围,以不同的顺序获取和释放锁

20 Hashtable的size()⽅法中明明只有⼀条语句”return count”,为什么还要做同步?

1、同⼀时间只能有⼀条线程执⾏固定类的同步⽅法,但是对于类的⾮同步⽅法,可以多条线程同时访问。所以,这样就有问题了,可能线程A在执⾏Hashtable的put⽅法添加数据,线程B则可以正常调⽤size()⽅法读取Hashtable中当前元素的个数,那读取到的值可能不是最新的,可能线程A添加了完了数据,但是没有对size++,线程B就已经读取size了,那 么对于线程B来说读取到的size⼀定是不准确的。

2、⽽给size()⽅法加了同步之后,意味着线程B调⽤size()⽅法只有在线程A调⽤put⽅法完毕之后才可以调⽤,这样就保证了线程安全性。

21 ConcurrentHashMap的并发度是什么?

1、⼯作机制(分⽚思想):它引⼊了⼀个“分段锁”的概念,具体可以理解为把⼀个⼤的Map拆分成N个⼩的segment,根据key.hashCode()来决定把key放到哪个HashTable中。可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。

2、应⽤:当读>写时使⽤,适合做缓存,在程序启动时初始化,之后可以被多个线程访问;

3、hash冲突:

简介:HashMap中调⽤hashCode()⽅法来计算hashCode。由于在Java中两个不同的对象可能有⼀样的hashCode,所以不同的键可能有⼀样hashCode,从⽽导致冲突的产⽣。hash冲突解决:使⽤平衡树来代替链表,当同⼀hash中的元素数量超过特定的值便会由链表切换到平衡树

4、⽆锁读:ConcurrentHashMap之所以有较好的并发性是因为ConcurrentHashMap是⽆锁读和加锁写,并且利⽤了分段锁(不是在所有的entry上加锁,⽽是在⼀部分entry上加锁);

读之前会先判断count(jdk1.6),其中的count是被volatile修饰的(当变量被volatile修饰后,每次更改该变量的时候会将更改结果写到系统主内存中,利⽤多处理器的缓存⼀致性,其他处理器会发现自⼰的缓存⾏对应的内存地址被修改,就会将自⼰处理器的缓存⾏设置为失效,并强制从系统主内存获取最新的数据。),故可以实现⽆锁读。

5、ConcurrentHashMap的并发度就是segment的⼤⼩,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最⼤优势。

22 ReentrantReadWriteLock读写锁的使⽤?

1、读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自⼰控制的,你只要上好相应的锁即可。

2、如果你的代码只读数据,可以很多⼈同时读,但不能同时写,那就上读锁;

3、如果你的代码修改数据,只能有⼀个⼈在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

23 CyclicBarrier和CountDownLatch的⽤法及区别?

CyclicBarrier和CountDownLatch 都位于java.util.concurrent 这个包下

24 LockSupport⼯具?

LockSupport是JDK中⽐较底层的类,⽤来创建锁和其他同步⼯具类的基本线程阻塞。java锁和同步器框架的核⼼ AQS:AbstractQueuedSynchronizer,就是通过调⽤ LockSupport .park()和 LockSupport .unpark()实现线程的阻塞和唤醒的。

25 Condition接⼝及其实现原理?

在java.util.concurrent包中,有两个很特殊的⼯具类,Condition和ReentrantLock,使⽤过的⼈都知道,ReentrantLock(重⼊锁)是jdk的concurrent包提供的⼀种独占锁的实现。

我们知道在线程的同步时可以使⼀个线程阻塞⽽等待⼀个信号,同时放弃锁使其他线程可以能竞争到锁。

在synchronized中我们可以使⽤Object的wait()和notify⽅法实现这种等待和唤醒。

但是在Lock中怎么实现这种wait和notify呢?答案是Condition,学习Condition主要是为了⽅便以后学习blockqueue和concurrenthashmap的源码,同时也进⼀步理解ReentrantLock。

26 Fork/Join框架的理解?

1、Fork就是把⼀个⼤任务切分为若⼲⼦任务并⾏的执⾏。

2、Join就是合并这些⼦任务的执⾏结果,最后得到这个⼤任务的结果。

27 wait()和sleep()的区别?

1、sleep() ⽅法是线程类(Thread)的静态⽅法,让调⽤线程进⼊睡眠状态,让出执⾏机会给其他线程,等到休眠时间结束后,线程进⼊就绪状态和其他线程⼀起竞争cpu的执⾏时间。

因为sleep() 是static静态的⽅法,他不能改变对象的机锁,当⼀个synchronized块中调⽤了sleep() ⽅法,线程虽然进⼊休眠,但是对象的机锁没有被释放,其他线程依然⽆法访问这个对象。

2、wait() wait()是Object类的⽅法,当⼀个线程执⾏到wait⽅法时,它就进⼊到⼀个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll⽅法来唤醒等待的线程。

28 线程的五个状态(五种状态,创建、就绪、运⾏、阻塞和死亡)?

线程通常都有五种状态,创建、就绪、运⾏、阻塞和死亡。

第⼀是创建状态。在⽣成线程对象,并没有调⽤该对象的start⽅法,这是线程处于创建状态。

第⼆是就绪状态。当调⽤了线程对象的start⽅法之后,该线程就进⼊了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运⾏之后,从等待或者睡眠中回来之后,也会处于就绪状态 。第三是运⾏状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进⼊了运⾏状态,开始运⾏run函数当中的代码。

第四是阻塞状态。线程正在运⾏的时候,被暂停,通常是为了等待某个时间的发⽣(⽐如说某项资源就绪)之后再继续运⾏。sleep,suspend,wait等⽅法都可以导致线程阻塞。

第五是死亡状态。如果⼀个线程的run⽅法执⾏结束或者调⽤stop⽅法后,该线程就会死亡。对于已经死亡的线程,⽆法再使⽤start⽅法令其进⼊就绪。

29 start()⽅法和run()⽅法的区别?

1、start()⽅法来启动⼀个线程,真正实现了多线程运⾏。

2、如果直接调⽤run(),其实就相当于是调⽤了⼀个普通函数⽽已,直接调⽤run()⽅法必须等待run()⽅法执⾏完毕才能执⾏下⾯的代码,所以执⾏路径还是只有⼀条,根本就没有线程的特征,所以在多线程执⾏时要使⽤start()⽅法⽽不是run()⽅法。

30 Runnable接⼝和Callable接⼝的区别?

Runnable接⼝中的run()⽅法的返回值是void,它做的事情只是纯粹地去执⾏run()⽅法中的代码⽽已;

Callable接⼝中的call()⽅法是有返回值的,是⼀个泛型,和Future、FutureTask配合可以⽤来获取异步执⾏的结果。

31 volatile关键字的作⽤?

多线程主要围绕可⻅性和原⼦性两个特性⽽展开,使⽤volatile关键字修饰的变量,保证了其在多线程之间的可⻅性,即每次读取到volatile变量,⼀定是最新的数据。

代码底层执⾏不像我们看到的⾼级语⾔—-Java程序这么简单,它的执⾏是Java代码–>字节码–>根据字节码执⾏对应的C/C++代码–>C/C++代码被编译成汇编语⾔–>和硬件电路交互,现实中,为了获取更好的性能JVM可能会对指令进⾏重排序,多线程下可能会出现⼀些意想不到的问题。使⽤volatile则会对禁⽌语义重排序,当然这也⼀定程度上降低了代码执⾏效率。

32 Java中如何获取到线程dump⽂件?

死循环、死锁、阻塞、⻚⾯打开慢等问题,查看线程dump是最好的解决问题的途径。所谓线程dump也就是线程堆栈,获取到线程堆栈有两步:

获取到线程的pid,可以通过使⽤jps命令,在Linux环境下还可以使⽤ps -ef | grep java

打印线程堆栈,可以通过使⽤jstack pid命令,在Linux环境下还可以使⽤kill -3 pid

另外提⼀点,Thread类提供了⼀个getStackTrace()⽅法也可以⽤于获取线程堆栈。这是⼀个实例⽅法,因此此⽅法是和具体线程实例绑定的,每次获取到的是具体某个线程当前运⾏的堆栈。

33 线程和进程有什么区别?

进程是系统进⾏资源分配的基本单位,有独⽴的内存地址空间

线程是CPU独⽴运⾏和独⽴调度的基本单位,没有单独地址空间,有独⽴的栈,局部变量,寄存器, 程序计数器等。

创建进程的开销⼤,包括创建虚拟地址空间等需要⼤量系统资源

创建线程开销⼩,基本上只有⼀个内核对象和⼀个堆栈。

⼀个进程⽆法直接访问另⼀个进程的资源;同⼀进程内的多个线程共享进程的资源。

进程切换开销⼤,线程切换开销⼩;进程间通信开销⼤,线程间通信开销⼩。

线程属于进程,不能独⽴执⾏。每个进程⾄少要有⼀个线程,成为主线程

34 线程实现的⽅式有⼏种(四种)?

继承Thread类,重写run⽅法

实现Runnable接⼝,重写run⽅法,实现Runnable接⼝的实现类的实例对象作为Thread构造函数的target

实现Callable接⼝通过FutureTask包装器来创建Thread线程

通过线程池创建线程

如果你想要学习Java的话,我给你分享一些Java的学习资料,你不用浪费时间到处搜了,从Java入门到精通的资料我都给你整理好了,这些资料都是我做Java这几年整理的Java最新学习路线,Java笔试题,Java面试题,Java零基础到精通视频课程,Java开发工具,Java练手项目,Java电子书,Java学习笔记,PDF文档教程,Java程序员面经,Java求职简历模板等,这些资料对你接下来学习Java一定会带来非常大的帮助,每个Java初学者都必备,请你进我的**Java技术qq交流群127522921**自行下载,所有资料都在群文件里,进去要跟大家多交流学习哦。

35 高并发、任务执⾏时间短的业务怎样使⽤线程池?并发不⾼、任务执⾏时间⻓的业务怎样使⽤线程池?并发⾼业务执⾏时间⻓的业务怎样使⽤线程池?

高并发、任务执⾏时间短的业务:线程池线程数可以设置为CPU核数+1,减少线程上下⽂的切换。

并发不⾼、任务执⾏时间⻓的业务要区分开看:

  1. 假如是业务时间⻓集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占⽤CPU,所以不要让所有的CPU闲下来,可以加⼤线程池中的线程数⽬,让CPU处理更多的业务

  2. 假如是业务时间⻓集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)⼀样吧,线程池中的线程数设置得少⼀些,减少线程上下⽂的切换

并发⾼、业务执⾏时间⻓,解决这种类型任务的关键不在于线程池⽽在于整体架构的设计,看看这些业务⾥⾯某些数据是否能做缓存是第⼀步,增加服务器是第⼆步,⾄于线程池的设置,设置参考(2)。最后,业务执⾏时间⻓的问题, 也可能需要分析⼀下,看看能不能使⽤中间件对任务进⾏拆分和解耦。

36 如果你提交任务时,线程池队列已满,这时会发⽣什么?

1、如果你使⽤的LinkedBlockingQueue,也就是⽆界队列的话,没关系,继续添加任务到阻塞队列中等待执⾏,因为LinkedBlockingQueue可以近乎认为是⼀个⽆穷⼤的队列,可以⽆限存放任务;

2、如果你使⽤的是有界队列⽐⽅说ArrayBlockingQueue的话,任务⾸先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使⽤拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。

37 锁的等级:⽅法锁、对象锁、类锁?

⽅法锁(synchronized修饰⽅法时):

通过在⽅法声明中加⼊ synchronized关键字来声明 synchronized ⽅法。

synchronized ⽅法控制对类成员变量的访问;

每个类实例对应⼀把锁,每个 synchronized ⽅法都必须获得调⽤该⽅法的类实例的锁⽅能执⾏,否则所属线程阻塞,⽅法⼀旦执⾏,就独占该锁,直到从该⽅法返回时才将锁释放,此后被阻塞的线程⽅能获得该锁,重新进⼊可执⾏状态。这种机制确保了同⼀时刻对于每⼀个类实例,其所有声明为 synchronized 的成员函数中⾄多只有⼀个处于可执⾏状态,从⽽有效避免了类成员变量的访问冲突。

对象锁(synchronized修饰⽅法或代码块):

当⼀个对象中有synchronized method或synchronized block的时候调⽤此对象的同步⽅法或进⼊其同步区域时,就必须先获得对象锁。如果此对象的对象锁已被其他调⽤者占⽤,则需要等待此锁被释放。(⽅法锁也是对象锁)

java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进⼊synchronized⽅法的时候获取该对象的锁,当然如果已经有线程获取了这个对象的锁,那么当前线程会等待;synchronized⽅法正常返回或者抛异常⽽终⽌,JVM会自动释放对象锁。这⾥也体现了⽤synchronized来加锁的1个好处,⽅法抛异常的时候,锁仍然可以由JVM来自动释放。

类锁(synchronized 修饰静态的⽅法或代码块):

由于⼀个class不论被实例化多少次,其中的静态⽅法和静态变量在内存中都只有⼀份。所以,⼀旦⼀个静态的⽅法被申明为synchronized。此类所有的实例化对象在调⽤此⽅法,共⽤同⼀把锁,我们称之为类锁。

对象锁是⽤来控制实例⽅法之间的同步,类锁是⽤来控制静态⽅法(或静态变量互斥体)之间的同步

38 如果同步块内的线程抛出异常会发⽣什么?

synchronized⽅法正常返回或者抛异常⽽终⽌,JVM会自动释放对象锁

39 并发编程(concurrency)并⾏编程(parallellism)有什么区别?

解释⼀:并⾏是指两个或者多个事件在同⼀时刻发⽣;⽽并发是指两个或多个事件在同⼀时间间隔发⽣。

解释⼆:并⾏是在不同实体上的多个事件,并发是在同⼀实体上的多个事件。

解释三:在⼀台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群所以并发编程的⽬标是充分的利⽤处理器的每⼀个核,以达到最⾼的处理性能。

40 如何保证多线程下 i++ 结果正确?

volatile只能保证你数据的可⻅性,获取到的是最新的数据,不能保证原⼦性;

⽤AtomicInteger保证原⼦性。

synchronized既能保证共享变量可⻅性,也可以保证锁内操作的原⼦性。

41 ⼀个线程如果出现了运⾏时异常会怎么样?

如果这个异常没有被捕获的话,这个线程就停⽌执⾏了。

另外重要的⼀点是:如果这个线程持有某个对象的监视器,那么这个对象监视器会被⽴即释放.

42 如何在两个线程之间共享数据?

通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进⾏唤起和等待,⽐⽅说阻塞队列BlockingQueue就是为线程之间共享数据⽽设计的。

43 ⽣产者消费者模型的作⽤是什么?

通过平衡⽣产者的⽣产能⼒和消费者的消费能⼒来提升整个系统的运⾏效率,这是⽣产者消费者模型最重要的作⽤。

解耦,这是⽣产者消费者模型附带的作⽤,解耦意味着⽣产者和消费者之间的联系少,联系越少越可以独自发展⽽不需要受到相互的制约。

44 怎么唤醒⼀个阻塞的线程?

如果线程是因为调⽤了wait()、sleep()或者join()⽅法⽽导致的阻塞;

suspend与resume:Java废弃 suspend() 去挂起线程的原因,是因为 suspend() 在导致线程暂停的同时,并不会去释放任何锁资源。其他线程都⽆法访问被它占⽤的锁。直到对应的线程执⾏ resume() ⽅法后,被挂起的线程才能继续,从⽽其它被阻塞在这个锁的线程才可以继续执⾏。但是,如果 resume() 操作出现在 suspend() 之前执⾏,那么线程将⼀直处于挂起状态,同时⼀直占⽤锁,这就产⽣了死锁。⽽且,对于被挂起的线程,它的线程状态居然还是 Runnable。

wait与notify:wait与notify必须配合synchronized使⽤,因为调⽤之前必须持有锁,wait会⽴即释放锁,notify则是同步块执⾏完了才释放

await与singal:Condition类提供,⽽Condition对象由new ReentLock().newCondition()获得,与wait和notify相同,因为使⽤Lock锁后⽆法使⽤wait⽅法

park与unpark:LockSupport是⼀个⾮常⽅便实⽤的线程阻塞⼯具,它可以在线程任意位置让线程阻塞。和Thread.suspenf()相⽐,它弥补了由于resume()在前发⽣,导致线程⽆法继续执⾏的情况。和Object.wait()相⽐,它不需要先获得某个对象的锁,也不会抛出IException异常。可以唤醒指定线程。如果线程遇到了IO阻塞,⽆能为⼒,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。

45 Java中⽤到的线程调度算法是什么

抢占式。⼀个线程⽤完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出⼀个总的优先级并分配下⼀个时间⽚给某个线程执⾏。

46 单例模式的线程安全性?

⽼⽣常谈的问题了,⾸先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建⼀次出来。单例模式有很多种的写法,我总结⼀下:

(1)饿汉式单例模式的写法:线程安全

(2)懒汉式单例模式的写法:⾮线程安全

(3)双检锁单例模式的写法:线程安全

47 线程类的构造⽅法、静态块是被哪个线程调⽤的?

线程类的构造⽅法、静态块是被new这个线程类所在的线程所调⽤的,⽽run⽅法⾥⾯的代码才是被线程自身所调⽤的。

48 同步⽅法和同步块,哪个是更好的选择?

同步块是更好的选择,因为它不会锁住整个对象(当然也可以让它锁住整个对象)。同步⽅法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停⽌执⾏并需要等待获得这个对象上的锁。

synchronized(this)以及⾮static的synchronized⽅法(⾄于static synchronized⽅法请往下看),只能防⽌多个线程同时执⾏同⼀个对象的同步代码段。

如果要锁住多个对象⽅法,可以锁住⼀个固定的对象,或者锁住这个类的Class对象。

synchronized锁住的是括号⾥的对象,⽽不是代码。对于⾮static的synchronized⽅法,锁的就是对象本身也就是this。

49 如何检测死锁?怎么预防死锁?

概念:是指两个或两个以上的进程在执⾏过程中,因争夺资源⽽造成的⼀种互相等待的现象,若⽆外⼒作⽤,它们都将⽆法推进下去。此时称系统处于死锁;

死锁的四个必要条件:

互斥条件:进程对所分配到的资源不允许其他进程进⾏访问,若其他进程访问该资源,只能等待,直⾄占有该资源的进程使⽤完成后释放该资源

请求和保持条件:进程获得⼀定的资源之后,⼜对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但⼜对自⼰获得的资源保持不放

不可剥夺条件:是指进程已获得的资源,在未完成使⽤之前,不可被剥夺,只能在使⽤完后自⼰释放

环路等待条件:是指进程发⽣死锁后,若⼲进程之间形成⼀种头尾相接的循环等待资源关系

死锁产⽣的原因:

因竞争资源发⽣死锁 现象:系统中供多个进程共享的资源的数⽬不⾜以满⾜全部进程的需要时,就会引起对诸资源的竞争⽽发⽣死锁现象

进程推进顺序不当发⽣死锁

检查死锁:

有两个容器,⼀个⽤于保存线程正在请求的锁,⼀个⽤于保存线程已经持有的锁。每次加锁之前都会做如下检测

检测当前正在请求的锁是否已经被其它线程持有,如果有,则把那些线程找出来

遍历第⼀步中返回的线程,检查自⼰持有的锁是否正被其中任何⼀个线程请求,如果第⼆步返回真,表示出现了死锁

死锁的解除与预防:

控制不要让四个必要条件成⽴。

50 HashMap在多线程环境下使⽤需要注意什么?

要注意死循环的问题,HashMap的put操作引发扩容,这个动作在多线程并发下会发⽣线程死循环的问题。

1、HashMap不是线程安全的;Hashtable线程安全,但效率低,因为是Hashtable是使⽤synchronized的,所有线程竞争同⼀把锁;⽽ConcurrentHashMap不仅线程安全⽽且效率⾼,因为它包含⼀个segment数组,将数据分段存储,给每⼀段数据配⼀把锁,也就是所谓的锁分段技术。

2、HashMap为何线程不安全:

put时key相同导致其中⼀个线程的value被覆盖;

多个线程同时扩容,造成数据丢失;

多线程扩容时导致Node链表形成环形结构造成.next()死循环,导致CPU利⽤率接近100%;

3、ConcurrentHashMap最⾼效;

51 什么是守护线程?有什么⽤?

守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程,这是它的作⽤——⽽其他的线程只有⼀种,那就是⽤户线程。所以java⾥线程分2种,

1、守护线程,⽐如垃圾回收线程,就是最典型的守护线程。

2、⽤户线程,就是应⽤程序⾥的自定义线程。

52 如何实现线程串⾏执⾏?a. 为了控制线程执⾏的顺序,如ThreadA->ThreadB->ThreadC->ThreadA循环执⾏三个线程,我们需要确定唤醒、等待的顺序。这时我们可以同时使⽤ Obj.wait()、Obj.notify()与synchronized(Obj)来实现这个⽬标。

线程中持有上⼀个线程类的对象锁以及自⼰的锁,由于这种依赖关系,该线程执⾏需要等待上个对象释放锁,从⽽ 保证类线程执⾏的顺序。

b. 通常情况下,wait是线程在获取对象锁后,主动释放对象锁,同时本线程休眠,直到有其它线程调⽤对象的notify()唤醒该线程,才能继续获取对象锁,并继续执⾏。⽽notify()则是对等待对象锁的线程的唤醒操作。但值得注意的是notify()调⽤后,并不是⻢上就释放对象锁,⽽是在相应的synchronized(){}语句块执⾏结束。释放对象锁后,JVM会在执⾏wait()等待对象锁的线程中随机选取⼀线程,赋予其对象锁,唤醒线程,继续执⾏。

53 可以运⾏时kill掉⼀个线程吗?

a. 不可以,线程有5种状态,新建(new)、可运⾏(runnable)、运⾏中(running)、阻塞(block)、死亡(dead)。

b. 只有当线程run⽅法或者主线程main⽅法结束,⼜或者抛出异常时,线程才会结束⽣命周期。

54 关于synchronized

在某个对象的所有synchronized⽅法中,在某个时刻只能有⼀个唯⼀的⼀个线程去访问这些synchronized⽅法

如果⼀个⽅法是synchronized⽅法,那么该synchronized关键字表示给当前对象上锁(即this)相当于synchronized(this){}

如果⼀个synchronized⽅法是static的,那么该synchronized表示给当前对象所对应的class对象上锁(每个类不管⽣成多少对象,其对应的class对象只有⼀个)

55 分步式锁,程序数据库中死锁机制及解决⽅案

基本原理:⽤⼀个状态值表示锁,对锁的占⽤和释放通过状态值来标识。

三种分布式锁:

第一种:Zookeeper:

基于zookeeper瞬时有序节点实现的分布式锁,其主要逻辑如下。⼤致思想即为:每个客户端对某个功能加锁时,在zookeeper上的与该功能对应的指定节点的⽬录下,⽣成⼀个唯⼀的瞬时有序节点。判断是否获取锁的⽅式很简单,只需要判断有序节点中序号最⼩的⼀个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁⽆法释放,⽽产⽣的死锁问题

【优点】锁安全性⾼,zk可持久化,且能实时监听获取锁的客户端状态。⼀旦客户端宕机,则瞬时节点随之消失,zk因⽽能第⼀时间释放锁。这也省去了⽤分布式缓存实现锁的过程中需要加⼊超时时间判断的这⼀逻辑。

【缺点】性能开销⽐较⾼。因为其需要动态产⽣、销毁瞬时节点来实现锁功能。所以不太适合直接提供给⾼并发的场景使⽤。

【实现】可以直接采⽤zookeeper第三⽅库curator即可⽅便地实现分布式锁。

【适⽤场景】对可靠性要求⾮常⾼,且并发程度不⾼的场景下使⽤。如核⼼数据的定时全量/增量同步等。)

第二种memcached:

memcached带有add函数,利⽤add函数的特性即可实现分布式锁。add和set的区别在于:如果多线程并发set,则每个set都会成功,但最后存储的值以最后的set的线程为准。⽽add的话则相反,add会添加第⼀个到达的值,并返回true,后续的添加则都会返回false。利⽤该点即可很轻松地实现分布式锁。

【优点】并发⾼效

【缺点】 memcached采⽤列⼊LRU置换策略,所以如果内存不够,可能导致缓存中的锁信息丢失。memcached⽆法持久化,⼀旦重启,将导致信息丢失。

【使⽤场景】⾼并发场景。需要 1)加上超时时间避免死锁; 2)提供⾜够⽀撑锁服务的内存空间; 3)稳定的集群化管理。

第三种Redis:

redis分布式锁即可以结合zk分布式锁锁⾼度安全和memcached并发场景下效率很好的优点,其实现⽅式和memcached类似,采⽤setnx即可实现。需要注意的是,这⾥的redis也需要设置超时时间,以避免死锁。可以利⽤jedis客户端实现。

2021年Java中高级面试必备知识点总结

在这个部分总结了2019年到目前为止Java常见面试问题,取其面试核心编写成这份文档笔记,从中分析面试官的心理,摸清面试官的“套路”,可以说搞定90%以上的Java中高级面试没一点难度。

本节总结的内容涵盖了:消息队列、Redis缓存、分库分表、读写分离、设计高并发系统、分布式系统、高可用系统、SpringCloud微服务架构等一系列互联网主流高级技术的知识点。

目录:

(上述只是一个整体目录大纲,每个点里面都有如下所示的详细内容,从面试问题——分析面试官心理——剖析面试题——完美解答的一个过程)

部分内容:

对于每一个做技术的来说,学习是不能停止的,小编把2019年到目前为止Java的核心知识提炼出来了,无论你现在是处于什么阶段,如你所见,这份文档的内容无论是对于你找面试工作还是提升技术广度深度都是完美的。

不想被后浪淘汰的话,赶紧搞起来吧,高清完整版一共是888页,需要的话可以点赞+关注

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
va中高级面试必备知识点总结

在这个部分总结了2019年到目前为止Java常见面试问题,取其面试核心编写成这份文档笔记,从中分析面试官的心理,摸清面试官的“套路”,可以说搞定90%以上的Java中高级面试没一点难度。

本节总结的内容涵盖了:消息队列、Redis缓存、分库分表、读写分离、设计高并发系统、分布式系统、高可用系统、SpringCloud微服务架构等一系列互联网主流高级技术的知识点。

目录:

[外链图片转存中…(img-YR2D30PN-1713152873450)]

(上述只是一个整体目录大纲,每个点里面都有如下所示的详细内容,从面试问题——分析面试官心理——剖析面试题——完美解答的一个过程)

[外链图片转存中…(img-m8fDqCwg-1713152873451)]

部分内容:

[外链图片转存中…(img-eRP3bZMd-1713152873451)]

[外链图片转存中…(img-zyMGT6NE-1713152873451)]

[外链图片转存中…(img-FaEIsDqn-1713152873451)]

对于每一个做技术的来说,学习是不能停止的,小编把2019年到目前为止Java的核心知识提炼出来了,无论你现在是处于什么阶段,如你所见,这份文档的内容无论是对于你找面试工作还是提升技术广度深度都是完美的。

不想被后浪淘汰的话,赶紧搞起来吧,高清完整版一共是888页,需要的话可以点赞+关注

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-jW8dPdVJ-1713152873452)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值