2023最新版多线程面试题(二)(持续更新中)

16、什么是并发容器的实现?

何为同步容器: 可以简单地理解为通过 synchronized 来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。比如 Vector,Hashtable以及 Collections.synchronizedSet,synchronizedList 等方法返回的容器。可以通过查看 Vector,Hashtable 等这些同步容器的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字 synchronized。

并发容器使用了与同步容器完全不同的加锁策略来提供更高的并发性和伸缩性例如在 ConcurrentHashMap 中采用了一种粒度更细的加锁机制,可以称为分段锁,在这种锁机制下,允许任意数量的读线程并发地访问 map,并且执行读操作的线程和写操作的线程也可以并发的访问 map,同时允许一定数量的写操作线程并发地修改 map,所以它可以在并发环境下实现更高的香吐量。

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

线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。

线程间的同步方法大体可分为两类: 用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态而用户模式就是不需要切换到内核态,只在用户态完成操作。用户模式下的方法有: 原子操作 (例如一个单一的全局变量) ,临界区。内核模式下的方法有:事件,信号量,互斥量。

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

当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则我们认为这发生了竞争条件 (race condition)。

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

新建状态 (New)

用 new 语句创建的线程处于新建状态,此时它和其他 Java 对象一样,仅仅在堆区中被分配了内存。

就绪状态 (Runnable)

当一个线程对象创建后,其他线程调用它的 start()方法,该线程就进入就绪状态Java 虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得 CPU 的使用权

运行状态 (Running)

处于这个状态的线程占用 CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。

阻塞状态 (Blocked)

阻塞状态是指线程因为某些原因放弃 CPU,暂时信止运行。当线程处于阴寒状态时,Java 虚拟机不会给线程分配 CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。

阻塞状态可分为以下3 种:

位于对象等待池中的阻塞状态 (Blocked in object’ s wait pool)

当线程处于运行状态时,如果执行了某个对象的 wait()方法,Java 虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。

位于对象锁池中的阻塞状态 (Blocked in object’ s lock pool) :

当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java 虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。

其他阻塞状态 (Otherwise Blocked) :

当前线程执行了 sleep()方法,或者调用了其他线程的 join()方法,或者发出了 I/0请求时,就会进入这个状态。

死亡状态 (Dead)

当线程退出 run()方法时,就进入死亡状态,该线程结束生命周期

/*时间,jvm 信息/
2017-11-01 17:36:28
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixedmode):
/*线程名称: DestroyJavaVM
编号: #13
优先级:5
系统优先级:0
jvm 内部线程id: 0x0000000001c88800
对应系统线程id (NativeThread ID) : 0x1c18
线程状态: waiting on condition [Ox0000000000000000] (等待某个条件)
线程详细状态: java.lang.Thread.State: RUNNABLE 及之后所有*/
"DestroyJavaVM"#13 prio=5 os prio= tid=0x0000000001c88800nid=0x1c18 waiting on condition [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE
"Thread-1"#12 prio=5 os prio=0 tid=0x0000000018d49000nid=0x17b8 waiting for monitor entry [0x0000000019d7f000]
/*线程状态: 阻塞 (在对象同步上)
    代码位置: at
com.leo.interview.SimpleDeadLock$B.run(SimpleDeadLock.java:56)
    等待锁: 0x00000000d629b4d8
    已经获得锁: 0x00000000d629b4e8*/
    java.lang.Thread.State: BLOCKED (on object monitor)
      at
com.leo.interview.SimpleDeadLock$B.run(SimpleDeadLock.java:56)
    - waiting to lock <0x00000000d629b4d8>(a java.lang.Object)
    - locked <0x00000000d629b4e8> (a java.lang.Object)
"Thread-0"#11 prio=5 os prio=0 tid=0x0000000018d44000 nid=0x1ebcwaiting for monitor entry [0x000000001907f000]
    java.lang.Thread.State: BLOCKED (on object monitor)
      at
com.leo.interview.SimpleDeadLock$A.run(SimpleDeadLock.java:34)
com.leo.interview.SimpleDeadLock$A.run(SimpleDeadLock.java:34)
    - waiting to lock <0x00000000d629b4e8> (a java.lang.Object)
    - locked <0x00000000d629b4d8> (a java.lang.Object)

"Service Thread"#10 daemon prio=9 os prio=0
tid=0x0000000018ca5000 nid=0x1264 runnable [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2"#9 daemon prio=9 os prio=2
tid=0x0000000018c46000 nid=0xb8c waiting oncondition[0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1"#8 daemon prio=9 os prio=2
tid=0x0000000018be4800 nid=0x1db4 waiting on condition[Ox0000000000000000]
    java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0"#7 daemon prio=9 os prio=2
tid=0x0000000018be3800 nid=0x810 waiting on condition[0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os prio=0
tid=0x0000000018bcc800 nid=0x1c24 runnable [Ox00000000193ce000]
    java.lang.Thread.State: RUNNABLE
      at java.net.SocketlnputStream.socketRead0(Native Method)
      at
java.net.SocketlnputStream.socketRead(SocketlnputStream.java:116)
    at java.netSocketlnputStream.read(SocketlnputStream.java:171)
    at java.net.SocketlnputStream.read(SocketlnputStream.java:141)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoderjava:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    - locked <0x00000000d632b928>(a java.io.lnputStreamReader)
    at java.io.lnputStreamReader.read(lnputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    - locked <0x00000000d632b928>(a java.io.lnputStreamReader)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at
com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" #5 daemon prio=5 os prio=2 tid=0x0000000017781800 nid=0x524 runnable [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os prio=2 tid=0x000000001778f800 nid=0x1b08 waiting on condition[0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

"Finalizer"#3 daemon prio=8 os prio=1 tid=0x000000001776a800 nid=Oxdac in Object.wait0 [0x0000000018b6f000]
    java.lang.ThreadState: WAITING (on object monitor)
      at java.lang.Object.wait(Native Method)
      - waiting on <0x00000000d6108ec8>(a
java.lang.ref.ReferenceQueueSLock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
    -locked <0x00000000d6108ec8>(a
java.lang.ref.ReferenceQueueSLock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os prio=2tid=0x0000000017723800 nid=0x1670 in Object.wait0[Ox00000000189ef000]
    java.lang.Thread.State: WAITING (on object monitor)
     at java.lang.Object.wait(Native Method)
     - waiting on <0x00000000d6106b68>(a
java.lang.ref.Reference$Lock)
     at java.lang.Object.wait(Object.java:502)
     at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
     - locked <0x00000000d6106b68> (a java.lang.ref.Reference$Lock)
     at java.lang.ref.ReferenceSReferenceHandler.run(Reference.java:153)

"VM Thread" os prio=2 tid=0x000000001771b800 nid=0x604 runnable

"GC task thread#0 (ParallelGC)" os prio=0 tid=0x0000000001c9d800nid=0x9fO runnable

"GC task thread#1 (ParallelGC)" os prio=0 tid=0x0000000001c9f000nid=0x1 54c runnable

"GC task thread#2 (ParallelGC)" os prio=0 tid=0x0000000001ca0800nid=0xcdO runnable

"VM Periodic Task Thread" os prio=2 tid=0x0000000018c5a000nid=0x1b58 waiting on condition

JNI global references: 33


/*此处可以看待死锁的相关信息! /
Found one Java-level deadlock:
=====================================
"Thread-1":
  waiting to lock monitor 0x0000000017729fc8 (object0x00000000d629b4d8,ajava.lang.Object),
  which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0000000017727738 (object0x00000000d629b4e8,a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================================
"Thread-1":
    at
com.leo.interview.SimpleDeadLock$B.run(SimpleDeadLock.java:56)
    - waiting to lock <0x00000000d629b4d8>(a java.lang.Object
    - locked <0x00000000d629b4e8>(a java.lang.Object)
"Thread-0":
    at
com.leo.interview.SimpleDeadLock$A.run(SimpleDeadLock.java:34)
    - waiting to lock <0x00000000d629b4e8> (a java.lang.Object)
    - locked <0x00000000d629b4d8>(a java.lang.Object)

Found 1 deadlock.
*内存使用状况,详情得看JVM 方面的书*/
Heap
PSYoungGen       total 37888K, used 4590K [0x00000000d61000000x00000000d8b00000,0x0000000100000000)
   eden space 32768K, 14% used
[0x00000000d6100000,0x00000000d657b968,0x00000000d8100000) 
   from space 5120K, 0% used
[0x00000000d8600000,0x00000000d8600000,0x00000000d8b00000)
   to space 5120K, 0% used
[0x00000000d8100000,0x00000000d8100000,0x00000000d8600000)
   ParOldGen    total 86016K,used 0K [Ox0000000082200000.0x0000000087600000,0x00000000d6100000)
   object space 86016K, 0% used
[0x0000000082200000,0x0000000082200000,0x0000000087600000)
Metaspace    used 3474K, capacity 4500K, committed 4864K,reserved 1056768K
class space  used 382K, capacity 388K, committed 512K, reserved104857 6K

20、为什么我们调用 start()方法时会执行 run()方法,为什么我们不能直接调用 run(方法?

当你调用 start0)方法时你将创建新的线程,并且执行在 run()方法里的代码但是如果你直接调用 run()方法,它不会创建新的线程也不会执行调用线程的代码只会把 run 方法当作普通方法去执行。

21、Java 中你怎样唤醒一个阻塞的线程?

在 Java 发展史上曾经使用 suspend0、resume()方法对于线程进行阻塞唤醒,但随之出现很多问题,比较典型的还是死锁问题。解决方案可以使用以对象为目标的阻塞,即利用 Object 类的 wait0和 notify()方法实现线程阻塞。

首先,wait、notify 方法是针对对象的,调用任意对象的 wait()方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取改对象的锁,直到获取成功才能往下执行;其次,wait、notify 方法必须在 synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。

22、在Java 中 CycliBarriar 和 CountdownLatch 有什么区别?

CyclicBarrier 可以重复使用,而 CountdownLatch 不能重复使用.

Java 的 concurrent 包里面的CountDownLatch 其实可以把它看作一个计数器只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器也就是同时只能有一个线程去减这个计数器里面的值。你可以向 CountDownLatch 对象设置一个初始的数字作为计数值,任何调用这个对象上的 await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为 0为止。

所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次一一计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。

CountDownLatch 的一个非常典型的应用场景是: 有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个 CountDownLatch 对象的 await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch 对象上的 countDown()方法,这个调用 await()方法的任务将一直阻塞等待,直到这个 CountDownLatch 对象的计数值减到 0为止。

CyclicBarrier 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

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

不可变对象(lmmutable Objects)即对象一旦被创建它的状态 (对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。不可变对象的类即为不可变类(lmmutable Class)。Java 平台类库中包含许多不可变类,如 String、基本类型的包装类、Biglnteger 和 BigDecimal 等。不可变对象天生是线程安全的。它们的常量 (域)是在构造函数中创建的。既然它们的状态无法修改,这些常量永远不会变。

不可变对象永远是线程安全的。
只有满足如下状态,一个对象才是不可变的;
它的状态不能在创建后再被修改;
所有域都是 final类型;并且,
它被正确创建 (创建期间没有发生 this 引用的逸出)

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

在上下文切换过程中,CPU 会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中上下文切换过程中的“页码”信息是保存在进程控制块 (PCB) 中的。PCB 还经

常被称作“切换桢”( switchframe)。“页码”信息会一直保存到 CPU 的内存中,直到他们被再次使用。
上下文切换是存储和恢复 CPU 状态的过程,它使得线程执行能够从中断点恢复执行。上下文切换是多任务操作系统和多线程环境的基本特征

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

计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU 的使用权才能执行指令,所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各自的任务.在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU的使用权

有两种调度模型: 分时调度模型和抢占式调度模型

分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU 的时间片这个也比较好理解。

java 虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

出世&入世

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值