java并发面试题,2024年最新java工程师面试突击

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

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

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

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

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

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

正文

}

下面是acquire() 的实现:

public final void acquire(int arg) {

//tryAcquire() 再次尝试获取锁,

//如果发现锁就是当前线程占用的,则更新state,表示重复占用的次数,

//同时宣布获得所成功,这正是重入的关键所在

if (!tryAcquire(arg) &&

// 如果获取失败,那么就在这里入队等待

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

//如果在等待过程中 被中断了,那么重新把中断标志位设置上

selfInterrupt();

}

公平锁和非公平锁区别

//非公平锁

final void lock() {

//上来不管三七二十一,直接抢了再说

if (compareAndSetState(0, 1))

setExclusiveOwnerThread(Thread.currentThread());

else

//抢不到,就进队列慢慢等着

acquire(1);

}

//公平锁

final void lock() {

//直接进队列等着

acquire(1);

}

非公平锁如果第一次争抢失败,后面的处理和公平锁是一样的,都是进入等待队列慢慢等。

3.Condition

概述

Condition接口可以理解为重入锁的伴生对象。它提供了在重入锁的基础上,进行等待和通知的机制。可以使用 newCondition()方法生成一个Condition对象。

private final Lock lock = new ReentrantLock();

private final Condition condition = lock.newCondition();

应用场景

在ArrayBlockingQueue阻塞队列中,就维护一个Condition对象

lock = new ReentrantLock(fair);

notEmpty = lock.newCondition();

这个notEmpty 就是一个Condition对象。它用来通知其他线程,ArrayBlockingQueue是不是空着的。当我们需要拿出一个元素时:

public E take() throws InterruptedException {

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (count == 0)

// 如果队列长度为0,那么就在notEmpty condition上等待了,一直等到有元素进来为止

// 注意,await()方法,一定是要先获得condition伴生的那个lock,才能用的哦

notEmpty.await();

//一旦有人通知我队列里有东西了,我就弹出一个返回

return dequeue();

} finally {

lock.unlock();

}

}

当有元素入队时:

public boolean offer(E e) {

checkNotNull(e);

final ReentrantLock lock = this.lock;

//先拿到锁,拿到锁才能操作对应的Condition对象

lock.lock();

try {

if (count == items.length)

return false;

else {

//入队了, 在这个函数里,就会进行notEmpty的通知,通知相关线程,有数据准备好了

enqueue(e);

return true;

}

} finally {

//释放锁了,等着的那个线程,现在可以去弹出一个元素试试了

lock.unlock();

}

}

private void enqueue(E x) {

final Object[] items = this.items;

items[putIndex] = x;

if (++putIndex == items.length)

putIndex = 0;

count++;

//元素已经放好了,通知那个等着拿东西的人吧

notEmpty.signal();

}

在这里插入图片描述

4.LockSupport类

LockSupport可以理解为一个工具类。它的作用很简单,就是挂起和继续执行线程。它的常用的API如下:

public static void park() : 如果没有可用许可,则挂起当前线程

public static void unpark(Thread thread):给thread一个可用的许可,让它得以继续执行

5.AbstractQueuedSynchronizer内部数据结构

在AbstractQueuedSynchronizer内部,有一个队列,我们把它叫做同步等待队列。它的作用是保存等待在这个锁上的线程(由于lock()操作引起的等待)。此外,为了维护等待在条件变量上的等待线程,AbstractQueuedSynchronizer又需要再维护一个条件变量等待队列,也就是那些由Condition.await()引起阻塞的线程。

在这里插入图片描述

在这里插入图片描述

可以看到,无论是同步等待队列,还是条件变量等待队列,都使用同一个Node类作为链表的节点。

在这里插入图片描述

6.AQS对资源的共享方式

在这里插入图片描述

AQS底层使用了模板方法模式

六、锁分类


1.互斥锁和自旋锁

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.乐观锁和悲观锁你的理解,使用场景

乐观锁
概念

先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。

使用场景

在线文档

我们都知道在线文档可以同时多人编辑的,如果使用了悲观锁,那么只要有一个用户正在编辑文档,此时其他用户就无法打开相同的文档了,这用户体验当然不好了。

SVN和Git

先让用户编辑代码,然后提交的时候,通过版本号来判断是否产生了冲突,发生了冲突的地方,需要我们自己修改后,再重新提交。

悲观锁
概念

认为多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁。

使用场景

互斥锁、自旋锁、读写锁,都是属于悲观锁。

3.读锁和写锁

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.可重入锁

在这里插入图片描述

七、线程


1.什么是线程安全

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其它的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

2.如何创建线程

1)继承Thread类

优势:编程比较简单,可以直接使用Thread类中的方法。

劣势:可扩展性较差,不能再继承其他的类。

2)实现Runnable接口

优势:扩展性强,实现该接口的同时还可以继承其他的类。

劣势:编程相对复杂,不能直接使用Thread类中的方法。

3)实现Callable接口

Callable 执行的任务有返回值,而 Runnable 执行的任务没有返回值。可以通过FutureTask中的get方法获取返回值。

Callable(重写)的方法是 call 方法,而 Runnable(重写)的方法是 run 方法。

call 方法可以抛出异常,而 Runnable 方法不能抛出异常。

4)使用线程池

在这里插入图片描述

3.线程间的通信方式

volatile关键字方式

利用volatile保证可见性,使得其他线程感受到共享变量的变化

等待/通知机制

基于Object类中的wait和notify方法

join方式

当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行。

threadLocal方式

4.wait和notify的底层实现

应用层面

在这里插入图片描述

jvm层面

前置工作

1)进入wait/notify方法之前,要获取synchronized锁

2)synchronized生成的字节码指令有monitorenter和 monitorexit,执行monitorenter指令可以获取对象的monitor

3)线程执行lock.wait()方法时,必须持有该lock对象的monitor,如果wait方法在synchronized代码中执行,该线程很显然已经持有了monitor。

4)在HotSpot虚拟机中,monitor采用ObjectMonitor实现。

在这里插入图片描述

5)ObjectMonitor对象中有两个队列:_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表;_owner指向获得ObjectMonitor对象的线程。

在这里插入图片描述

_WaitSet :处于wait状态的线程,会被加入到wait set;

_EntryList:处于等待锁block状态的线程,会被加入到entry set;

6)ObjectWaiter对象是双向链表结构,保存了_thread(当前线程)以及当前的状态TState等数据, 每个等待锁的线程都会被封装成ObjectWaiter对象。

在这里插入图片描述

wait方法实现

lock.wait()方法最终通过ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);实现:

1)将当前线程封装成ObjectWaiter对象node;

在这里插入图片描述

2)通过ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中;

在这里插入图片描述

3)通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象。

在这里插入图片描述

4)最终底层的park方法会挂起线程;

notify方法实现

lock.notify()方法最终通过ObjectMonitor的void notify(TRAPS)实现:

1)如果当前_WaitSet为空,即没有正在等待的线程,则直接返回;

2)通过ObjectMonitor::DequeueWaiter方法,获取_WaitSet列表中的第一个ObjectWaiter节点,实现也很简单。

这里需要注意的是,在jdk的notify方法注释是随机唤醒一个线程,其实是第一个ObjectWaiter节点

在这里插入图片描述

3)根据不同的策略,将取出来的ObjectWaiter节点,加入到_EntryList或则通过

5.线程切换方式

在这里插入图片描述

6.程序一般开多少线程

在这里插入图片描述

7.notify和notifyAll区别

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

8.wait和sleep的区别

在这里插入图片描述

八、线程池


1.线程池优点

降低资源消耗

重用存在的线程,减少对象创建销毁的开销。

提高响应速度

可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

提高线程的可管理性

线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

附加功能

提供定时执行、定期执行、单线程、并发数控制等功能。

2.线程池有哪几种

newCachedThreadPool

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

newFixedThreadPool

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

newScheduledThreadPool

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

newSingleThreadExecutor

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

3.线程池参数

corePoolSize: 线程池核心线程数最大值

maximumPoolSize: 线程池最大线程数大小

keepAliveTime: 线程池中非核心线程空闲的存活时间大小

unit: 线程空闲存活时间单位

workQueue: 存放任务的阻塞队列

threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。

handler: 线城池的拒绝策略事件,主要有四种类型。

4.线程池的拒绝策略

在这里插入图片描述

5.执行execute()方法和submit()方法的区别

在这里插入图片描述

6.线程池工作原理

在这里插入图片描述

九、并发常见类


1.ConCurrentHashMap

出现背景

1)HashMap线程不安全,在1.7中采用头插法会造成死循环,在1.8中改为尾插法,会造成元素覆盖。

2)HashTable集合类和Collections下的SynchronizedMap类是线程安全的,但是会锁住整个表,效率低下。

jdk1.7和1.8中采用的技术

1)jdk1.7中ConcurrentHashMap采用锁分段技术,每个部分是一个Segment。

2)jdk1.8中采用Synchronized + CAS,把锁的粒度进一步降低

jdk1.7的底层原理
存储结构

在这里插入图片描述

1)采用链表加数组的数据结构,把原来的整个table划分为n个 Segment 。每个 Segment 里边是由 HashEntry 组成的数组,每个 HashEntry之间又可以形成链表。

2)当对某个 Segment 加锁时,并不会影响到其他 Segment 的读写。

put方法的流程

1)通过哈希算法计算出当前 key 的 hash 值

2)通过这个hash值找到它所对应的Segment数组的下标

3)再通过hash值计算出它在对应Segment的HashEntry数组 的下标

4)找到合适的位置插入元素

size方法底层实现

首先采用乐观的方式,认为统计 size 的过程中,并没有发生 put, remove 等会改变 Segment 结构的操作。遍历统计count和modcount的个数,其中count指的是每个Segment元素的个数,modcount指的是每次 table 结构修改时,如put,remove等,此变量都会自增。如果发生了修改,则需要重试,重试两次都不成功,则需要把所有segment加锁之后再计算。

jdk1.8底层原理
存储结构

数组+链表+红黑树,不再有Segment的概念,而是给数组中的每一个头节点(桶)都加锁,使用的是Synchronized 锁。在jdk1.6之后,Synchronized引入了锁升级的概念。

put方法

若当前桶为空,则通过 CAS 原子操作,把新节点插入到此位置

2.AtomicInteger类的原理

在这里插入图片描述

在这里插入图片描述

3.CountDownLatch

在这里插入图片描述

4.CyclicBarrier

一组线程会互相等待,直到所有线程都到达一个同步点。这个就非常有意思了,就像一群人被困到了一个栅栏前面,只有等最后一个人到达之后,他们才可以合力把栅栏(屏障)突破。

5.Semaphore

在这里插入图片描述

十、ThreadLocal


1.作用

在这里插入图片描述

2.应用场景

在这里插入图片描述

3.底层原理

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

十一、并发编程题


1.手写死锁

package com.shenhao;

public class Demo {

public static void main(String[] args) {

Object objA = new Object();

Object objB = new Object();

new Thread(() -> {

while(true){

synchronized (objA){

//线程一

synchronized (objB) {

System.out.println(“小A正在走路”);

}

}

}

}).start();

new Thread(() -> {

while(true){

synchronized (objB){

//线程二

synchronized (objA) {

System.out.println(“小B正在走路”);

}

}

}

}).start();

}

}

2.手写生产者消费者

生产者

package com.shenhao.threaddemo16;

import java.util.concurrent.ArrayBlockingQueue;

public class Producer extends Thread{

private ArrayBlockingQueue abq;

public Producer(ArrayBlockingQueue abq){

this.abq = abq;

}

@Override

public void run() {

while(true){

try {

abq.put(“用品”);

System.out.println(“生产者生产一个用品”);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

消费者

package com.shenhao.threaddemo16;

import java.util.concurrent.ArrayBlockingQueue;

public class Consumer extends Thread{

private ArrayBlockingQueue abq;

public Consumer(ArrayBlockingQueue abq){

this.abq = abq;

}

@Override

public void run() {

while(true){

String s = null;

try {

s = abq.take();

System.out.println(“消费者消费” + s);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

主程序调用

package com.shenhao.threaddemo16;

import java.util.concurrent.ArrayBlockingQueue;

public class Demo {

public static void main(String[] args) {

//阻塞队列,容量为1

ArrayBlockingQueue abq = new ArrayBlockingQueue<>(1);

Producer p = new Producer(abq);

Consumer c = new Consumer(abq);

p.start();

c.start();

}

}

3.手写阻塞队列

阻塞队列

最后

光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性

Java面试宝典2021版

最常见Java面试题解析(2021最新版)

2021企业Java面试题精选

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

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

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

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

主程序调用

package com.shenhao.threaddemo16;

import java.util.concurrent.ArrayBlockingQueue;

public class Demo {

public static void main(String[] args) {

//阻塞队列,容量为1

ArrayBlockingQueue abq = new ArrayBlockingQueue<>(1);

Producer p = new Producer(abq);

Consumer c = new Consumer(abq);

p.start();

c.start();

}

}

3.手写阻塞队列

阻塞队列

最后

光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性

Java面试宝典2021版

[外链图片转存中…(img-smIzwXKA-1713547961847)]

[外链图片转存中…(img-FeYZj7wR-1713547961848)]

最常见Java面试题解析(2021最新版)

[外链图片转存中…(img-JC7YxIY0-1713547961848)]

[外链图片转存中…(img-BU6L3Vem-1713547961849)]

2021企业Java面试题精选

[外链图片转存中…(img-BWrLdEWt-1713547961849)]

[外链图片转存中…(img-lZhFtZHs-1713547961849)]

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值