多线程面试题及答案

1. 多线程有什么用?

发挥CPU的优势,提升系统性能。

防止程序阻塞。

2. 线程和进程的区别

        进程是程序运行和资源分配的基本单位,一个程序中至少有一个进程,一个进程至少要有一个线程,进程有独立的地址空间,而线程则是进程中的一个执行路径,线程有自己的堆栈和局部变量,没有地址空间。

3. java实现线程有哪几种方式?
  • 继承Thread类。
  • 实现Runnable接口。
  • 实现Callable接口。
4. Runnable接口和Callable接口有什么区别?

        Runnable接口中的run()方法没有返回值,而Callable接口中的call()方法返回的是一个泛型,可以和Futrue、FutrueTask一起使用可以用来获取异步执行的结果。

5. start()方法和run()方法有什么区别?

start()方法是用来启动线程的,真正实现多线程的运行。

run()方法是线程具体执行的代码块。

6. 如何终止一个线程?

stop 终止,不推荐。

LockSupport 可以协商终止线程。

7. 一个线程的周期有哪几种状态?

        当new一个线程时,线程处于新建状态,线程调用start()方法时并且CPU没有切换到当前线程时,线程处于等待状态。当CPU切换到当前线程时,处于运行状态,当CPU从当前线程切换到其他线程或者是等待锁,或者休眠时,当前线程处于阻塞状态。线程全部执行完后,则处于死亡状态。

8. 线程中sleep()和wait()的区别?

        sleep休眠不会释放锁资源,也不用等待唤醒,而wait休眠时,会释放锁资源,需要用notify()或notifyAll()唤醒。

9. notify()和notifyAll()的区别?

        notify()只会唤醒一个wait()休眠的线程,具体唤醒哪个由JVM决定和优先级有关系,而notifyAll()则是唤醒所有wait休眠的线程,被唤醒的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁,调用notify()后只有一个线程会由等待池进入锁池,而notifyAll()会将该对象等待池内的所有线程移动到锁池中等待竞争。

10. 并行和并发有什么区别?

        并行是指两个或多个事件在同一时刻发生,并发是指两个或多个事件扎起同一时间段发生的事情。

11. 守护线程是什么?

守护线程是服务线程的,准确来说就是服务其他的线程。

12. 为什么要使用线程池?

        我们知道如果不用线程池,每个线程都要new Thread()来创建线程,线程比较少的时候没有问题,当线程达到一定数量之后,就会耗尽系统的CPU和内存资源,也会造成GC的频繁收集和停顿,每次创建和销毁都要消耗资源,造成资源浪费。

13. 线程池的工作原理?

线程池的工作原理主要由三方面组成:线程状态、线程池的重要属性和线程的工作流程。

线程池的5中状态:

        运行状态(Running):当前状态下,线程池可以接受新的任务,也可以处理阻塞队列中的任务,执行shutdown()方法进入待关闭状态,执行hutdownNow()方法可进入停止状态。

        待关闭状态(shutDown):当前状态下,线程池不再接受新的任务,继续处理阻塞队列中的任务。当阻塞队列中的任务为空,且工作线程数为0的时候,进入整理状态。

        停止状态(stop):当前状态下,线程池不接受新任务,也不处理阻塞队列中的任务,反而会尝试结束执行中的任务,当工作线程为0是,进入整理状态。

        整理状态(tidYing):当前状态下,所有任务都已经执行完毕,且没有工作线程。执行rerminated()方法进入终止状态。

        终止状态(terminated):当前状态下,线程池完全终止,并完成了所有资源的释放。

线程池的重要属性:

        一个线程池的核心参数有很多,每个参数都有着特殊的作用,各个参数聚合在一起完成整个线程池的工作。其中的6个尤为重要:线程状态和工作线程数,核心线程数和最大线程数,创建线程的工厂,缓存任务的阻塞队列,非核心线程存活时间和拒绝策略。

  1. 线程状态和工作线程数量:
    1. 首先线程池是有状态的,在不同的状态下,线程池的行为是不一样的。
    2. 然后线程池肯定是需要线程去执行具体的任务,所以在线程池中就封装了一个内部类Worker作为工作线程,每个Worker中都维持着一个Thread。
    3. 线程池的重点之一,就是空值线程资源合理高效的使用,所以必须控制工作线程的个数,所以需要保存当前线程池中工作线程的个数。
  2. 核心线程数和最大线程数:​​​​​​现在有了标识工作线程的个数的变量,那到底该多少线程才合适呢?线程多了会浪费线程资源,少了又不能够发挥线程池的性能,所以线程池涉及了两个变量协作,分别是:
    1. 核心线程数:用来表示线程池中的核心线程的数量,也可以称为可闲置的线程数量。
    2. 最大线程数:用来表示线程池中最多能够创建的线程数量。
  3. 创建线程的工厂:既然是线程池,那自然就少不了线程,线程的创建时由线程工厂ThreadFactory来完成的。
  4. 缓存任务的阻塞队列:上面我们说了核心线程数和最大线程数,并且也介绍了工作线程的个数在0到最大线程数之间的变化,但不可能一下子就创建所有线程,把线程池装满,而是由一个过程:
    1. 当线程池接受到一个任务时,如果工作线程数没有达到核心线程数,就会创建一个新的线程,并绑定该任务,直到工作线程的数量达到核心线程数之前都不会重用之前创建的线程,当工作线程达到核心线程数时,这时又接到新的任务,会将任务存放到阻塞队列中等待核心线程去执行。为什么不直接创建更多新的线程呢?原因是核心线程中很可能已经有线程执行完自己的任务了,或者有其他线程马上就能处理完当前的任务,并且接下来就能投入到新的任务中去,所以阻塞队列是一种纯冲机制,给核心线程一个机会让他们充分发挥自己的能力,另外一个值得考虑的原因是,创建线程毕竟代价昂贵的,不可能一有任务就去创建一个新的线程,所以我们需要为线程池配备一个阻塞队列,用来临时缓存任务,这些任务等待工作线程来执行。
  5. 非核心线程存活时间: 当工作线程达到核心线程数时,线程池会将新接收到的任务放到阻塞队列中,而阻塞队列分为两种情况:
    1. 有界队列:当阻塞队列中塞满了等待执行的任务,这时再有新任务提交时,线程池就需要创建新的临时线程来处理。但是创建临时线程是有存活时间的,当阻塞队列中的任务被执行完毕并且没有那么多新任务被提交时,临时线程就需要被回收销毁,而在被回收销毁之前等待的这段时间就是非核心线程的存活时间。
    2. 无界队列:当核心线程都在忙时,所有新提交的任务都会被存放在该无界队列中,这时最大线程数将变的没有意义,因为阻塞队列不会存在被装满的情况。
  6. 拒绝策略:虽然我们有了阻塞队列来对任务进行缓存,从一定程度上为线程池提供了缓冲期,但是如果时有界队列,那阻塞队列满的情况也存在工作线程的数量已经达到最大线程数时,如果这个时候再有新的任务提交时,显然线程池已经心有余而力不足了,因为既没有空闲的队列空间来存放该任务,也无法创建新的线程来执行该任务了,所以我们就需要一个拒绝策略。

        拒绝策略时一个RejectedExecutionHandler类型的变量,用户可以自行指定拒绝策略,如果不指定,线程池将使用默认的拒绝策略:抛出异常。

                  其他拒绝策略:

                        直接丢弃该任务。

                        使用调用者线程执行该任务。

                        丢弃任务队列中的最老的一个任务,然后提交该任务。

线程池工作流程:

        提交任务->创建工作线程->启动工作线程->获取任务并执行->销毁线程

        提交任务: 在向线程池提交一个新的任务时, 线程池有三种处理情况, 分别是: 创建一个工作线程来执行任务, 将任务加入阻塞队列, 拒绝该任务

                提交任务的过程可以拆分为几个部分:                        

                        1 当工作线程数小于核心线程数时, 直接创建新的核心工作线程

                        2 当工作线程数大于核心线程数时, 就需要尝试将任务添加到阻塞队列中去

                        3 如果能够加入成功, 说明队列还没有满, 那么就需要做以下的二次校验来保证添加进去的任务能够成功被执行.

                        4 验证当前线程中的运行状态, 如果是非运行(running)状态, 则需要将任务从阻塞队列中移除, 然后拒绝该任务

                        5 验证当前线程池中的工作线程个数, 如果是0,则需要主动添加一个空工作线程来执行刚刚添加到阻塞队列中的任务.

                        6 如果加入失败, 说明队列已经满了,这时就需要创建新的临时工作线程来执行任务.

                        7 如果创建成功, 则直接执行该任务

                        8 如果创建失败, 说明工作线程已经等于最大线程数, 只能拒绝该任务了

        创建工作线程:

                创建工作线程需要做一系列的判断, 需要确保当前线程池可以创建新的线程之后,才能创建.

                第一, 当线程池的状态是待关闭状态(shutDown) 或者是关闭状态(stop)时, 不能创建新的线程.

                第二, 当线程工厂创建线程失败时, 也不能创建新的线程.

                第三, 拿当前工作线程的数量和核心线程数, 最大线程数进行比较 , 如果前者大于后者, 不允许创建

                第四 , 线程池会尝试通过CAS 来自增工作线程的个数, 如果自增成功 , 则会创建新的工作线程, 即worker对象, 然后加锁进行二次验证是否能够创建工作线程, 如果最后创建成功, 则会启动工作线程。

        启动工作线程:

                当工作线程创建成功之后 , 也就是worker对象已经创建好了, 这时就需要启动该工作线程, 让线程开始干活, worker对象中关联着一个Thread, 所以要启动工作线程的话, 只要通过worker.Thread.start() 来启动即可

        获取任务并执行:

                在runWorker方法被调用之后, 就是执行具体的任务了, 首先需要拿到一个可以执行的任务, 而Worker对象中默认绑定一个任务, 如果改任务不为空的话, 就直接执行.

                执行完之后, 就会去阻塞队列中获取任务来执行。

                获取任务的过程需要考虑当前工作线程的个数:

                        如果工作线程数大于核心线程数, 就需要通过 poll(keepAliveTime, timeUnit) 来获取 , 因为这时需要对闲置线程进行超时回收。

                        如果工作线程数小于等于核心线程数, 那就可以通过take()来获取. 因为这时所有的线程都是核心线程, 不需要进行回收, 前提是没有设置 允许核心线程超时回收 为 true。

14. 创建线程池的几种方式?
  1. newFixedThreadPool(n)创建一个固定长度的线程池。
  2. newCachedThreadPool(n)创建一个可缓存的线程池,如果线程池的规模超过了处理请求,将自动回收空闲线程,而当增加时则可以自动添加新线程,线程池的规模不存在任何限制
  3. newSingleThreadExcecutor()这时一个单线程的Exceutor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替换它,它的特点是能确保任务在队列中是按照顺序串行执行的。
  4. newScheduledThreadPool(n)创建一个固定长度的线程池,而且以延迟活定时的方式来执行任务。
15. 线程池中submit()和execute()方法的区别?

        execute没有返回值,如果不需要知道线程的结果,就使用execute()方法,性能会好很多,submit()方法返回一个Futrue对象,如果想知道线程的结果就使用submit提交,而且它能在主线程中通过Futrue的get方法捕获线程中的异常接收参数不一样,submit()方法有返回值,而execute()方法没有,submit()方法方便异常处理。

16. 什么叫线程安全?

线程安全:

        当多个线程同时访问同一个对象的时候,如果不考虑运行时的调度和交替执行,也不需要进行额外的同步时,调用这个对象都可以获得正确的结果,我们就称之为线程安全。

17. Java中如何保证线程的运行安全?
  1. 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作。
  2. 可见性:一个线程对主内存的修改可以及时的被其他线程看到。
  3. 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排,该观察结果一般是杂乱无序的。
18. 什么是死锁?如何防止死锁产生?

死锁就是两个线程都在等待对方释放锁才能继续执行,就是死锁状态。

发生死锁的四个必要条件:

  • 互斥条件
  • 请求和保持条件
  • 不可剥夺条件
  • 环路等待条件

四个条件只要其中一个不满足就不会发生死锁情况。

19. synchronized的原理?

        synchronized 是JVM中的一个锁,它通过对象内部的一个叫做监视器锁来实现的,监视器锁本质上又是依赖于底层的操作系统的 muterLock(互斥锁)来实现的,而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要较长时间,这就是为什么 synchronized 效率低的原因。

        同步方法通过 ACC_SYNCHRONIZED关键字隐式的对方法进行加锁,当线程要执行的方法被标注上 acc_synchronized 时,需要先获得锁才能执行该方法,同步代码块通过monitorenter和monitorexit执行来进行加锁,当线程到 monitorenter 的时候要先获得锁,才能执行后面的方法,当线程执行到 montorexit 的时候则需要释放锁,每个对象自身维护着一个被加锁次数的计数器,当计数器不为0时,只有获得锁的线程才能再次获得锁。

20. synchronized与volatile的区别?

        volatile 只能修饰实例变量和类变量,而 synchronized 可以修饰方法,以及代码块,volatile 可以保证数据的可见性,但不能保证原子性(多线程进行读写,不能保证线程安全),而 synchronized 是一种排他(互斥)的机制,volatile用于禁止指令排序,可以解决单例双重检查对象初始化代码执行杂乱无章的问题.

        volatile 可以看做是轻量级的synchronized,volatile 不能保证原子性,但是如果对一个共享变量进行线程赋值,那么可以用 volatile替代synchronized,因为赋值本身是具有原子性的,而volatile又保证了可见性,所以也可以保证了线程安全。

21. synchronized与lock的区别?

        首先 synchronized是java内置关键字,在JVM层面,而lock是java的一个类。synchronized无法判断是否获取到锁,而lock可以判断是否获取到锁,synchronized会自动释放锁,而lock需要手动释放锁,否则很容易造成死锁。synchronized的锁可重入,不可中断,非公平,而lock锁是可重入的,可判断,可公平的,synchronized锁适合代码少量的同步问题,lock锁适合大量同步的代码同步问题。

        lock锁保证可见性的方式和synchronized完全不同,synchronized基于它的内存语义,在获取和释放时,对cpu缓存做了一个同步到主内存的操作。

        lock锁是基于volatile实现的。lock锁内部在进行加锁和释放锁时,会对一个由volatile修饰的state属性进行加减操作。

        如果对volatile修饰的属性进行写操作,cpu会执行带有lock前缀的指令,cpu会将修改的数据,从cpu缓存立即同步到主内存,同时也会将其他的属性也立即同步到主内存中,还会将其他cpu缓存进行中的这个数据设置为无效,必须重新从主内存中拉取。

22. 简单描述以下ABA问题?

        假设有两个线程T1、T2同时去修改一个变量的值,都想从A修改到B,首先T1对值进行更新 A->B。T2由于某种原因处于等待状态,就在这时恰好有个线程T3进来了将值从B->A,当T2进行更新时发现值还是A,便进行了A->B的个更新,这就是ABA问题。

        解决方案就是加版本号 或者 是时间戳来解决。

23. 多线程之间是如何通信的?
  1. 通过共享变量,变量需要通过volatile修饰。
  2. 使用wait()方法和notifyAll()方法,但是由于需要使用同一把锁,所以必须通知线程释放锁,这样导致通知不及时。
  3. 使用countDownLatch实现,通知线程到指定条件,调用countDownLatch.countDown()方法被通知线程进行countDownLatch.await()。
  4. 使用condition的await()方法和signalAll()方法。
24. synchronized关键字加在静态方法和实例方法的区别?

        修饰静态方法是对类进行加锁,如果该类中A方法和B方法都被synchronized修饰,此时线程1 2分别调用方法A B,则线程2会阻塞到线程1执行完成之后才能执行。

        修饰实例方法时是对实例进行加锁,锁的是实例对象的对象头,如果调用同一个对象的两个不同的被synchronized修饰的实例方法,看到的效果和上面的一样,如果调用不同对象的两个不同的被synchronized修饰的实例方法,则不会阻塞。

25. synchronized锁的膨胀过程?

当线程访问同步代码块,首先查看当前锁状态是否是偏向锁(可偏向状态):

  • 如果是偏向锁:
    • 检查当前mark word中记录是否是当前线程id,如果是当前线程id,则获得偏向锁执行同步代码块。
    • 如果不是当前线程id,CAS操作替换线程id,替换成功获得偏向锁(线程复用),替换失败锁撤销升级轻量级锁(同一类对象多次撤销升级达到阈值20,则批量重偏向)
  • 升级轻量级锁:
    • 升级轻量级锁对当前线程,分配栈帧锁记录 lock_record (包含mark word 和object-指向锁记录着地址),对象头 mark word复制到线程栈帧的锁记录 mark word 存储的是无锁的 hashCode(里面有重入次数问题)
  • 重量级锁:
    • CAS自旋达到一定次数升级为重量级锁(多线程同时竞争锁时)存储在objectmonitor对象,里面有很多属性 ContentionList、EntryList, Waitset, owner,当一个线程尝试获取锁时,如果该锁已经被占用,则该线程封装成objectwaiter对象插到ContentionList队列的队首,然后调用park挂起, 该线程锁释放时会从ContentionList 或 EntryList 挑一个唤醒, 线程获得锁后调用object 的wait() 方法 则会加入到 waitset集合中(当前锁或膨胀为重量级锁)

注意:偏向锁在1.6之后默认开启,开启程序启动几秒后才会被激活。

26. CountDownLatch的用法?
  1. 让主线程await,业务线程进行处理,处理完成时调用countDown()、CountDownLatch实例化的时候需根据业务去选择CountDownLatch的count。
  2. 让业务先await,主线程处理完数据之后进行countDown(),此时业务线被唤醒,然后去主线程拿数据或者执行自己的业务逻辑。
27. 描述以下AQS?
  • AQS中定义了一个状态变量state,他有两种使用方法:
    • 互斥锁:当AQS只实现为互斥锁的时候,每次只要更新state的值从0到1 成功了就获取了锁,可重入时通过不断把state源自更新加1实现的。
    • 互斥锁+共享锁:当AQS需要同时实现互斥锁的时候,低16位存储互斥锁的状态,高16位存储共享锁的状态,主要用于实现读写锁
      • 互斥锁是一种独占锁,每次只允许一个线程独占,且当一个线程独占时,其他线程将无法获取互斥锁及共享锁,但是他自己可以获取共享锁。
      • 共享锁同时允许多个线程占有,只要一个线程占有了共享锁,所有线程都将无法再获取互斥锁,但可以获取共享锁。
  • AQS队列:
    • AQS中维护了一个队列,获取锁失败的线程都将进入这个队列中排队等待锁释放后唤醒下一个排队的线程(互斥锁模式下)。
    • condition队列:AQS中还有另一个非常重要的内部类 condition object,它实现了Condition接口,主要用于实现条件锁。
      • ConditionObject中也维护了一个队列, 这个队列主要用于等待条件的成立 , 当条件成立时,其他线程将 signal 这个队列中的元素, 将其移动到AQS的对列中, 等待占有锁的线程释放锁后被唤醒。
      • Condition 典型的运用场景是在 BlockingQueue 中的实现, 当队列为空时,获取元素的线程阻塞在noEmpty 条件上, 一但队列中添加了一个元素,将通知notEmpty 条件, 将其队列中的元素移动到AQS队列中等待被唤醒。
28. volatile的功能?
  • 保证线程可见性。
  • 防止指令重排序。
29. volatile的可见性和禁止指令重排序是怎么实现的?
  • 可见性 : volatile 的功能就是被修饰的变量在被修改后可以立即同步到主内存, 被修饰的变量在每次使用之前都从主内存刷新, 本质也是内存屏障来实现可见性的。
    • 写内存屏障 : 可以促使处理器将当前 store buffer 的值写回主内存。
    • 读内存屏障 : 可以促使处理器处理失效队列 , 进而避免由于 store buffer和 invalidate Queue 的非实时性带来的问题。
  • 禁止指令重排序 : volatile 是通过内存屏障来禁止指令重排序JMM内存屏障的策略。
    • 在每个volatile 写操作的前面插入一个 storeStore 屏障。
    • 在每个volatile 写操作的后面插入一个 storeLoad 屏障。
    • 在每个volatile 读操作的前面插入一个 loadLoad 屏障。
    • 在每个volatile 读操作的后面插入一个 loadStore 屏障。
30. 什么CAS?有什么优缺点?
  1. 由于其非阻塞性,它对死锁问题天生免疫,并且线程间的相互影响也远远比基于锁的方式要小。更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此它要比基于锁定方式拥有更优越的性能。
  2. 无锁的好处:
    1. 在高并发的情况下,它比有锁的程序拥有更好的性能。
    2. 它天生就是死锁免疫。
  3. CAS算法的过程:它包含三个参数cas(v e n)V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做,最后CAS返回当前V的真实值。
  4. CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功的完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出, 并成功更新,其余均会失败.失败的线程不会被挂起来,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作. 基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰 ,并进行恰当的处理。
  5. 简单的说,CAS需要你给出一个期望值,也就是你认为这个变量现在应该是什么样子的.如果变量不是你想象的那样, 说明它已经被别人修改过了.你就重新读取 再次尝试修改就好了。
31. 什么是ThreadLocal?

        ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供自己的变量副本,所以每个线程都可以独立的改变自己的副本,从而不会影响其他线程所对应的副本。

32. 在Queue中poll()和remove()有什么区别?

        poll()和remove()都是从队列中删除一个元素,但是,当poll()在获取元素失败的时候会返回空,而remove()失败的时候会抛出异常。

33. ReentrantLock分公平锁和非公平锁,那底层是如何实现的?

首先不管是公平锁和非公平锁,它们的底层实现都会使用AQS来进行排队,它们的区别在于线程在使用lock()方法加锁时:

  • 如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果有线程在排队,则当前线程也进行排队。
  • 如果是非公平锁,则不会检查是否有线程在排队,而是直接竞争锁。
34. ReenterantLock中tryLock()和lock()方法的区别?

        tryLock()表示尝试加锁,可能加到,也可能加不到,该方法不会阻塞线程,如果加到锁则返回true,没有加到则返回false。

        lock()表示阻塞加锁,线程会阻塞直到加到锁,方法也没有返回值。

35. synchronized和ReentrantLock的区别?
  1. synchronized是一个关键字,ReentrantLock是一个类。
  2. synchronized会自动加锁与释放锁,ReentrantLock需要程序员手动加锁与释放锁。
  3. synchronized的底层是jvm层面的锁,ReentrantLock是API层面的锁。
  4. synchronized是非公平锁,ReentrantLock可以选择公平锁或非公平锁。
  5. synchronized锁的是对象,所信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来标识锁的状态。
  6. synchronized底层有一个锁升级的过程。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一些Java多线面试以及它们的答案: 1. 什么是线程? 线程是操作系统能够进行运算调度的最小单位。在Java中,线程是轻量级的执行单元,可以和其他线程共享内存,但它们有各自的栈空间,执行不同的任务。 2. Java中如何创建线程? Java中有两种方式创建线程:继承Thread类和实现Runnable接口。继承Thread类需要重写run()方法,实现Runnable接口需要实现run()方法,然后将其传入Thread类的构造方法中。 3. 线程的生命周期有哪些状态? 线程的生命周期有5种状态:创建状态、就绪状态、运行状态、阻塞状态和死亡状态。创建状态是指线程被创建但还没有启动;就绪状态是指线程已经准备好运行但还没有被调度;运行状态是指线程正在执行;阻塞状态是指线程暂停执行,等待某些条件满足后再继续执行;死亡状态是指线程执行完毕或者因为异常而终止。 4. 什么是线程安全? 线程安全是指多个线程同时访问共享资源时,不会产生不正确的结果。在Java中,可以使用synchronized关键字和Lock接口来实现线程安全。 5. 什么是死锁? 死锁是指多个线程因为互相等待对方释放资源而无法继续执行的一种情况。在Java中,可以使用synchronized关键字和Lock接口来避免死锁的发生。 6. 什么是volatile关键字? volatile关键字可以保证线程之间的可见性,即一个线程修改了变量的值,其他线程可以立即看到修改后的结果。同时,volatile关键字也可以禁止指令重排序,保证代码的执行顺序。 7. 什么是线程池? 线程池是一种管理线程的机制,它可以在程序启动时预先创建一定数量的线程,并且将这些线程保存在一个池中,供需要执行任务的线程使用。线程池可以避免频繁创建和销毁线程的开销,提高程序的性能。在Java中,可以使用ThreadPoolExecutor类来实现线程池。 以上是常见的Java多线面试答案,希望能够对您有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值