• 大厂面试都在问的高并发问题汇总【精选】,附代码案例


    在 java 中守护线程和本地线程区别?

    任何的线程都可以设置为守护线程或者用户线程,通过thread.setDaemon就可以设置。这个设置必须要在thread.satrt之前设置,否则就会报错。

    可以简单的理解为守护线程是jvm创建的(但这不绝对),用户线程是应用程序创建的。比如jvm的垃圾回收线程就是守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时,Java 虚拟机会自动离开。。Thread Dump 打印出来的线程信息,含有 daemon 字样的线程即为守护进程,可能会有:服务守护进程,编译守护进程,GC守护进程等等

    线程与进程的区别?协程

    进程是资源分配的最小单位,线程是操作系统调度的最小单位

    一个进程至少有 一个进程,同一进程内的线程之间的资源共享

    协程(用户态):

    • 因为函数(子程序)不是线程切换,而是由程序自身控制的,因此没有线程切换的开销;

    • 和多线程比,线程数量越多,协程的性能优势越明显

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

    多线程共同使用计算机上的一组CPU,当线程数量大于CPU数量的时候,为了让所有的线程都有机会执行,需要轮转切换cpu,当不同线程使用CPU时发生的数据切换就叫做上下文切换

    死锁与活锁的区别,死锁与饥饿的区别?

    死锁:多个线程因为互斥的抢占一个资源而互相等待,如果没有借助外力就不会 放弃

    死锁产生的必要条件:

    互斥:某一资源在同一时间内只能被一个线占有

    循环等待:形成了一种首尾相连的环

    请求并保持:不会放弃资源等待

    不被剥夺条件:已经占有资源的线程,在未使用完前不能被剥夺

    活锁:任务或者事件由于没有成功,会失败,重试,而不是不是一直等待下去,活锁可以自行解开资源 等待,就是破坏了死锁的一些必要条件

    饥饿: 线程由于一直无法满足执行条件,导致一直无法执行,造成了饥饿。处于一直无法执行的状态、

    java中导致饥饿的原因:

    一直被高优先级的线程抢占资源,自身无法获取资源

    线程等待一个自身处于等待完成的对象(比如调用这个对象的wait方法)

    解决办法:选取比较合适的线程调度算法

    什么是线程组,为什么在 Java 中不推荐使用?

    线程组是ThreadGroup类,它在里面既可以有线程对象,也会有线程组对象,有点类似于树状结构 ,不用是因为有安全问题

    为什么使用 Executor 框架?

    传统的new thread方式创建销毁线程的开销很大,而且随着new thread 被无限制创建,会导致由于系统资源被耗尽而瘫痪的问题。这些线程缺乏管理,线程之前的交互也会销号较多的资源。Excutor Service较new thread有更好的服务支持,对开发者很友好,比如定时定期执行,中断

    在 Java 中 Executor 和 Executors 的区别?

    Executors 工具类可以根据我们的需求创建线程池满足业务需求

    Executor 接口方法可以执行线程任务

    ExcutorService继承了Excutor接口并且丰富了功能,通过这个服务我们可以管理线程,获取线程的执行状态或者返回值

    例如 ThreadPoolExutor可以创建自定义线程池

    Feture 表示异步计算结果方法,它可以清楚计算执行的状态和结果的返回,通过get方法获取计算的结果,通过then来完成后续需要这个结果的操作,是一个相当不错的在平时业务开发中会用到的特性。

    什么是原子操作?在 Java Concurrency API 中有哪些原子类(atomic classes)?

    原子操作是java中一个不被中断,不受其他线程操作影响的任务单元,CAS原语就是Compare&Swap

    在atomic包下提供了很多一些原子操作类对int或者long都提供了原子操作类

    还有针对一些结合也提供了原子操作 类。当线程调用这些原子对象的方法的时候,这些操作是排他的,不会被打断,当其他线程就会像自旋锁一样,等待这个对象执行完成,并且加入任务队列中,当前方法执行完成后,jvm会从队列中取出一个任务继续执行

    缺陷:

    ABA问题的原子操作类:AtomicMarkbleReference(通过加入一个boolean值来判断中间是否发生过变化)

    AtomicStampedReference(通过加入一个int类型变量来判断中间是否发生过变化)

    cpu的空转

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

    Lock接口较synchronized提供了更多丰富的功能,同步只支持非公平锁

    而lock支持公平锁,轮询锁(try lock),无条件锁,定时锁,可中断锁

    什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型?

    阻塞队列是一个支持两个附加操作的队列

    支持当线程从队列里获取对象的时候,如果为空则阻塞。如果线程往队列里面丢数据的时候,如果阻塞队列满了就不可以 操作

    java里提供了多种阻塞队列数据结构:

    ArrayBlockingQueue: 阻塞队列数组

    LinkedBlockingQueue

    SynchronizedQueue

    priorityBlockingQueue

    在java 5版本之前为了实现生产者消费者。生产者模型,可以使用一个普通的集合,通过多个线程协作和同步实现目的,。在java5之后使用生产者,消费者模式通过阻塞队列就可以轻易实现

    一个典型的应用场景就是,在Soket客户端的数据读取中,读取线程往阻塞队列生产数据,解析线程从阻塞队列消费数据

    什么是 Callable 和 Future?

    Callable和Runnable 类似,从名字就可以看出,不同的是,Runnable被线程执行后不会返回结果,并且不会抛出返回结果的异常。Callable可以返回线程执行的结果,这个返回的结果可以被Future通过get()方法获取。

    准确点说Future可以获取异步任务的返回值。总体来说是Callable用于产生结果,而Future用于接收结果

    什么是 FutureTask?

    FutureTask是指一个异步可取消计算的任务。它提供了启动,取消计算,获取计算结果。如果计算还没有结束,调用get方***一直阻塞,直至结果返回,FutureTask对象可以对调用了Callbale和Runnable的对象进行封装。由于FutureTask也调用Runnable接口,所以可以交给ExcutorService执行

    什么是并发容器的实现?

    与并发容器相对的是同步容器。

    同步容器在多线程的场景下他们会串行的执行 ,他们的线程安全版本就是在方法中加入syschronized关键字。java中的同步容器比较有代表性的是Vector,hashTable,同步容器是对整个容器对象加锁,所以其吞吐率并不高。

    并发容器使用与同步容器不同的加锁策略来控制并发访问,例如在ConcurrentHashMap中使用了更加细粒度的分段锁,当有线程对对象方法进行调用时,通过只对部分加锁,来实现多线程并发地对容器进行读取和写入操作 ,提高吞吐量。

    多线程同步和互斥有几种实现方法,都是什么?

    线程同步指的是多个线程之间一种制约关系,一个线程地执行依赖另外一个线程地结果,当还没取得结果时线程应该处于阻塞状态。线程互斥指的多个线程访问同一进程内的共享资源时,当资源被一个线程获取了,其他要使用该线程的资源就必须等待。可以理解为互斥时一种特殊的同步。

    线程同步方法大致分为两类:用户态和内核态。内核态指使用系统内核的单一性来进行同步,需要由用户态到内核态的切换。用户模式就就不需要

    线程的同步方法大致分为:

    用户态下的同步方法有:临界区,原子操作 内核态:信号量,管程,事件

    什么是竞争条件?你怎样发现和解决竞争?

    当多个进程或者线程对共享资源进行某种处理,但最后的结果又取决于线程执行顺序的时候,这时候我们就觉得这个发生了竞争

    12345678910111213141516171819202122// 创建2个线程的线程池 Executor executor = Executors.newFixedThreadPool(2);while(存在未对账订单){ // 计数器初始化为2 CountDownLatch latch = new CountDownLatch(2); // 查询未对账订单 executor.execute(()->{ pos = getPOrders(); latch.countDown(); }); // 查询派送单 executor.execute(()->{ dos = getDOrders(); latch.countDown(); }); // 等待两个查询操作结束 latch.await(); // 执⾏对账操作 diff = check(pos, dos); // 差异写⼊差异库 save(diff); }

    你将如何使用 thread dump?你将如何分析 Thread dump?

    runnable 就绪

    blcoking 阻塞

    running 运行

    New 新建

    在 Java 中 CycliBarriar 和 CountDownLatch有什么区别?

    CountDownLatch 用游戏里的一句话就叫别浪等人到齐了再团,countDownLatch在初始化的时候会设置一个int值,在调用await的时候这个方***一直阻塞,直到变量变为0的时候,会释放所有的线程继续往下执行

    CycliBarriar 是多个线程互相等待,直到所有的线程到达屏障后会一起执行。等待线程都释放后,这个barrier又可以重用

    而信号量适用于有固定的资源,需要已经占有资源的线程释放了才可以用用。

    cyclicBarrier

    12345678910111213141516171819202122232425262728293031323334353637383940414243444546{ // 创建 CyclicBarrier 实例,计数器的值设置为2 private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2); public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); int breakCount = 0; // 将线程提交到线程池 executorService.submit(() -> { try { System.out.println(Thread.currentThread() + "第一回合"); Thread.sleep(1000); cyclicBarrier.await(); System.out.println(Thread.currentThread() + "第二回合"); Thread.sleep(2000); cyclicBarrier.await(); System.out.println(Thread.currentThread() + "第三回合"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }); executorService.submit(() -> { try { System.out.println(Thread.currentThread() + "第一回合"); Thread.sleep(2000); cyclicBarrier.await(); System.out.println(Thread.currentThread() + "第二回合"); Thread.sleep(1000); cyclicBarrier.await(); System.out.println(Thread.currentThread() + "第三回合"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }); executorService.shutdown(); } }

    123456Thread[pool-1-thread-1,5,main]第一回合Thread[pool-1-thread-2,5,main]第一回合Thread[pool-1-thread-1,5,main]第二回合Thread[pool-1-thread-2,5,main]第二回合Thread[pool-1-thread-1,5,main]第三回合Thread[pool-1-thread-2,5,main]第三回合

    countDownLatch

    123456789101112131415161718192021222324final CountDownLatch latch = new CountDownLatch(2); new Thread() { public void run() { System.out.println("子线程" + Thread.currentThread().getName() + "正在执行"); Thread.sleep(3000); System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕"); latch.countDown(); }}.start(); new Thread() { public void run() { System.out.println("子线程" + Thread.currentThread().getName() + "正在执行"); Thread.sleep(3000); System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕"); latch.countDown(); }}.start(); System.out.println("等待 2 个子线程执行完毕...");latch.await();System.out.println("2 个子线程已经执行完毕");System.out.println("继续执行主线程");

    1234567等待 2 个子线程执行完毕...子线程Thread-0正在执行子线程Thread-1正在执行子线程Thread-0执行完毕子线程Thread-1执行完毕2 个子线程已经执行完毕继续执行主线程

    什么是不可变对象,它对写并发应用有什么帮助?

    一个对象一旦被创建后就不能被改变,包括对象的数据也即对象的属性不能随意改变

    Java中提供的String,基本类型的包装类,BigInteger,BigDecimal

    他们是线程安全,属性的设置是在构造函数中完成的

    Java 中用到的线程调度算法是什么?

    计算机中的cpu在同一时刻只能执行一条机器指令,线程只有获得CPU得使用权才能进入运行状态。线程调度算法目的就是让在运行池中的多个线程轮流获得cpu得使用权执行各自得任务。java虚拟机得一项任务,就是根据特性得调度机制,为线程分配cpu使用权。

    cpu调度算法大致分为:分时调度或者抢占式调度

    分时调度:大概就是cpu轮询的为每个就绪的线程分配cpu时间片,平均分配每个cpu的使用时间

    抢占式调度:让运行池中的线程具有高优先级的线程先获得cpu的使用权,低优先级的后获取

    乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

    悲观锁:每次进行拿数据的时候,总以为会修改,所以每次在拿数据的时候要先拿到锁,其他的线程就必须等待直到获得这个锁才可以访问,早期的数据库的很多操作都是基于乐观锁和悲观锁实现的,例如:表锁、行锁、读锁、写锁。synchronzied关键字就是通过悲观锁实现的

    乐观锁:乐观锁是在每次进行修改的时候,拿数据的时候不觉得会修改所以不会上锁,但是在更新的时候会检查这期间有没有人更新数据,通常的做法是加个版本号来解决,这样可以提高吞吐量。在java atomic包下面的原子类就是通过乐观锁的CAS的方式实现的

    乐观锁的实现方式:

    1.提供一个版本号来判断查到的数据和修改的数据是否一致,如果不一致提供失败或者重试的机制

    2.java中的CAS,当java中多个线程尝试修改CAS变得时候的时候,只有一个线程能够线程,其他的线程并不是因此而终止,而是被告知在这次竞争中失败,可以再次尝试。在CAS中有三个值,要修改的内存位置,预期比较值,拟写新值,会比较前面说的两个,如果不一样就可以更新

    CAS缺点:

    针对ABA的问题,但是JDK后面引入了两个类 一个是AtomicRemarkableReference解决和AtomicStampedReference解决

    循环开销大

    不支持对多个变量执行操作

    CopyOnWriteArrayList 可以用于什么应用场景

    最终一致性,读写分离

    CopyOnWriteArrayList是无锁容器,当容器进行修改操作的时候,会复制一份副本,读取线程可以安全的操作。所以不会出现ConcureentModificationException并发修改异常的问题

    这个将会导致下面的问题:

    1. 由于读取和写入在 不同的对象上 进行,需要拷贝数组,会导致占用内存,如果内存占用过高将会导致频繁发生,会影响性能

    2. 不具备实时性,调用set之后数据可能还是旧的,能做到最终一致性

    CopyOnwriteArrayList的一些思想:

    1. 读写分离

    2. 最终一致性

    3. 开辟新的空间解决并发修改问题

    什么叫线程安全?servlet 是线程安全吗?

    多线程安全指的是,多个线程调用函数或者函数包,多线程之间能够正确处理他们的共享变量,使程序能正确的运行

    java中 原子性,内存可见性,禁止指令重排

    severlet是单实例多线程的,当多个线程访问同一个方法的时候,是不保证线程安全性的

    volatile 有什么用?能否用一句话说明下 volatile 的应用场景?

    volatile保证多线程下的内存可见性和禁止指令重排

    volatile用于多线程环境下的单次读或者单次写操作

    为什么代码会重排序

    在程序 执行期间,编译器和处理器为了性能会对指令进行重排,但是排序不是任何重排的需要满足以下条件:

    1.单线程执行不影响最终结果的正确性

    2.存在数据依赖的不允许指令重排

    重排可能会影响多程序执行的语义

    为什么 wait, notify 和 notifyAll 这些方法不在 thread类里面?

    很显然他们是对象级的锁,每个对象都有锁,wait和notify以及notifyall都是对象及的锁

    什么是 ThreadLocal 变量

    每个线程都有一个ThreadLocal变量,也就说每个线程都有自己的一个独立变量,线程竞争不安全就完全消除了。它为创建代价高昂对象提供了线程安全的方法,例如说可以用ThreadLocal让SimpelDataFormat变成线程安全的,因为那个对象创建现需要创建多个实例,代价高昂所以不值得在局部范围去使用他,如果为每个变量,将大大提高效率。通过复用减少代价高昂对象的创建个数,其次你在不使用代价高昂的同步的情况下就实现了线程安全

    什么是线程池? 为什么要使用它?

    创建线程要消耗大量的资源,如果一个任务来了才创建线程,那么这个任务的相应时间将会变得很长,而且进程能创建的线程十分有限。创建线程池的主要目的就是资源复用和管理。线程池就是在进程启动的时候,创建若干线程来处理响应,里面的线程又叫工作线程,

    Java 中的 ReadWriteLock 是什么?

    读写锁是用来提升并发程序性能的锁分离技术的成果。读支持并行,只要有写还是会互斥

    copyon writelist 就是

    volatile 变量和 atomic 变量有什么不同

    前者不具备原子性 后者具备原子性

    你如何确保 main()方法所在的线程是 Java 程序最后结束的线程?

    通过Thread.join()方法确保所有创建的线程在main方法退出前结束

    线程之间是如何通信的?

    volatile 通过内存一致性才控制 :共享变量

    中断

    join

    对象锁:notiy wait notifyall

    管道

    信号量

    如何确保线程安全?

    使用同步

    原子类

    并发容器

    使用线程安全类

    使用不变类

    阻塞队列的底层原理,手动实现一个

    12345678910111213141516171819202122232425262728293031323334353637383940414243444546import java.util.LinkedList;import java.util.List;import java.util.concurrent.locks.ReentrantLock; public class MyBlockingQueue { /** * 容量 **/ int capacity; /** * 队列 **/ List<String> list; public MyBlockingQueue(int capacity){ this.capacity = capacity; this.list = new LinkedList<>(); } public synchronized void put(String s){ while(list.size()>=capacity){ try { this.wait(); }catch (InterruptedException e){ e.printStackTrace(); } } list.add(s); this.notifyAll(); } public synchronized String take(){ while(list.size()<=0){ try{ this.wait(); }catch (InterruptedException e){ e.printStackTrace(); } } this.notifyAll(); return list.remove(0); } }

    volatile关键字

    volatile是一种轻量级在有限资源条件下保障线程安全的技术,它保证了修饰变量的内存可见性和有序性,但非原子性。当对于syschronized更加高效,常常和synchronized混用。使用场景是在不执行非原子操作的场景中。

    线程更新变量过程:

    1. 主存存储线程需要操作的变量,但是线程不直接操作主存

    2. 线程需要操作变量时都是先从主存中拷贝一份到线程工作内存中

    3. 线程操作完之后再写回主存中

    volatile通过在使用时必须执行读取和加载内存,工作内存操作完之后必须同步到主存中,由于禁止了指令重排,保证了操作的连续性。所以这个流程看上去是连续的,保证了内存的及时更新。

    ReentrantLock和synchronize的区别

    联系,两个都是通过获得锁还进行同步,syschronized是隐式锁,线程不能操控锁。但是rentrantlock是显示锁,提供了获取锁,尝试获取锁 ,中断,释放,条件监控变量,控制当前lock线程的wait和notify,例如阻塞队列LinkedBlockingQueue就是实现原理 ,也很大成都上依托了reentrantlock的这些特性。

    api层面;syschronized可以直接作用于代码块,reentrantlock需要通过lock 和finally unlock来控制锁的获取与释放。同步支持锁住代码块和方法,但是reentrantlock只支持锁住代码块。

    中断方面;reentrantlock 支持中断,如果等待了很长时间任然获取不到,可以中断等待去做其他的事情,但是syschronized不会

    公平性: 就是按照线程的等待顺序获取锁这个叫公平的,syschronized只支持非公平锁,但是reentrantlock 即可实现公平的也可以实现非公平的。

    锁定条件: syschronzied中通过wait和notufy以及notufyall可以实现一个隐含的条件。reentrantlock可以通过newCondition绑定多个锁定条件 ,进行多个条件之间的交互。

    自旋锁、读写锁、互斥锁的区别

    互斥锁: 互斥锁加锁之后,线程会释放cpu,给其他线程。获取线程资源是由cpu控制的,而不是用户态,内核自动去控制唤醒

    自旋锁: 自旋锁加锁失败之后,线程会忙等待,直到它拿到锁,是在用户态实现的,由cas实现,如果确定等待的时间很短可以用,没有cpu开销

    读写锁:

    读写锁从字面意思我们也可以知道,它由「读锁」和「写锁」两部分构成,如果只读取共享资源用「读锁」加锁,如果要修改共享资源则用「写锁」加锁。

    公平读写锁比较简单的一种方式是:用队列把获取锁的线程排队,不管是写线程还是读线程都按照先进先出的原则加锁即可,这样读线程仍然可以并发,也不会出现「饥饿」的现象。

    sychronized的自旋锁、偏向锁、轻量级锁、重量级锁,分别介绍和联系

    无锁:

    就是一个资源在同一个时刻只能被一个线程访问和修改

    偏向锁:

    第一次执行syschronized代码块的时候,偏向锁就是偏向于第一个获得该获取它的线程的锁。执行完同步代码块之后并不会主动释放偏向锁。线程访问完资源之后,不会主动释放锁,第二次访问资源的时候,会去检查获取锁的线程是否是自身,如果是不需要加锁

    偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,此时持syschronized升级为自旋锁。

    自旋锁

    自旋锁是一种轻量级锁

    自旋锁启用的时期有偏向锁关闭的时候,或者多个线程竞争偏向锁的时候,升级为自旋锁

    什么叫线程竞争: 就是线程在访问共享资源的时候,不存在阻塞的状态。当线程获取锁的时候发现,锁已经被其他的线程占用,只能等待,这时候就发生了竞争。

    通过在用户 态进行自旋地去获取锁,获取锁地过程其实就是循环访问和修改CAS的过程

    自旋的过程十分消耗cpu,一个线程在持有锁,其他线程在原地自旋,为了防止长时间的自旋,虚拟机默认允许10次自旋,超过这个自旋次数,就会升级为重量级锁

    重量级锁

    如果锁竞争情况严重,达到某个最大自旋次数的某个线程,会将轻量级的锁升级为重量级锁。当后续的线程再来获取锁的时候,如果已经被其他线程获取锁不再是自旋,而是暂时挂起,等待被唤醒。就是将锁的获取过程交给操作系统来管理,通过线程调度算法来变更

    这里为大家整理了一份面试PDF文档,感兴趣且需要的朋友关注+私信【学习】或vx公众号【小马的后端宇宙】即可获取哦

  • 相关阅读:
    【Robotframework+python】实现http接口自动化测试
    Python unicode编码转中文
    golang RPC包的使用和源码学习(下):tinyrpc项目源码学习
    如何设计一张数据库表
    【软考软件评测师】第三十二章 数据库系统基础知识
    【Vuex+ElementUI】Vuex中取值存值以及异步加载的使用
    利用NoteExpress统一Elsevier旗下期刊参考文献格式
    Mac电脑清理软件有哪些 MacBooster和CleanMyMac哪个好用 苹果电脑清理垃圾软件推荐 cleanmymac和柠檬清理
    使用MASA Stack+.Net 从零开始搭建IoT平台 第五章 使用时序库存储上行数据
    java培训技术自定义视图介绍
  • 原文地址:https://blog.csdn.net/JavaMonsterr/article/details/125546902