Java面试之并发篇(一)
1、前言
本篇主要总结JAVA面试中关于并发相关的高频面试题。本篇的面试题基于网络整理,和自己编辑。在不断的完善补充哦。
2、简述程序、进程、线程、的基本概念?
2.1、程序
程序,是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
2.2、进程
进程,是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
2.3、线程
线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
2.4、三者之间的关系
- 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
- 从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
3、 线程有什么优缺点?
3.1、好处
- 使用多线程可以把程序中占据时间长的任务放到后台去处理,如图片、视屏的下载。
- 发挥多核处理器的优势,并发执行让系统运行的更快、更流畅,用户体验更好。
3.2、坏处
- 大量的线程降低代码的可读性。
- 更多的线程需要更多的内存空间。
- 当多个线程对同一个资源出现争夺时候要注意线程安全的问题。
4、你了解守护线程吗?它和非守护线程有什么区别?
Java 中的线程分为两种:守护线程(Daemon)和用户线程(User)。
- 任何线程都可以设置为守护线程和用户线程,通过方法
Thread#setDaemon(boolean on)
设置。true
则把该线程设置为守护线程,反之则为用户线程。Thread#setDaemon(boolean on)
方法,必须在Thread#start()
方法之前调用,否则运行时会抛出异常。
唯一的区别是:
程序运行完毕,JVM 会等待非守护线程完成后关闭,但是 JVM 不会等待守护线程。
- 判断虚拟机(JVM)何时离开,Daemon 是为其他线程提供服务,如果全部的 User Thread 已经撤离,Daemon 没有可服务的线程,JVM 撤离。
- 也可以理解为守护线程是 JVM 自动创建的线程(但不一定),用户线程是程序创建的线程。比如,JVM 的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时,Java 虚拟机会自动离开。
扩展:Thread Dump 打印出来的线程信息,含有 daemon 字样的线程即为守护进程。可能会有:服务守护进程、编译守护进程、Windows 下的监听 Ctrl + break 的守护进程、Finalizer 守护进程、引用处理守护进程、GC 守护进程。
5、什么是多线程上下文切换?
多线程会共同使用一组计算机上的 CPU ,而线程数大于给程序分配的 CPU 数量时,为了让各个线程都有执行的机会,就需要轮转使用 CPU 。
不同的线程切换使用 CPU 发生的切换数据等,就是上下文切换。
- 在上下文切换过程中,CPU 会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。PCB还经常被称作“切换桢”(switchframe)。“页码”信息会一直保存到CPU的内存中,直到他们被再次使用。
- 上下文切换是存储和恢复 CPU 状态的过程,它使得线程执行能够从中断点恢复执行。上下文切换是多任务操作系统和多线程环境的基本特征。
6、Java 中用到的线程调度算法是什么?
假设计算机只有一个 CPU ,则在任意时刻只能执行一条机器指令,每个线程只有获得 CPU 的使用权才能执行指令。
- 所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各自的任务。
- 在运行池中,会有多个处于就绪状态的线程在等待 CPU ,Java 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。
有两种调度模型:分时调度模型和抢占式调度模型。
- 分时调度模型是指让所有的线程轮流获得 CPU 的使用权,并且平均分配每个线程占用的 CPU 的时间片这个也比较好理解。
Java 虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用 CPU ,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用 CPU 。处于运行状态的线程会一直运行,直至它不得不放弃 CPU 。
如非特别需要,尽量不要用,防止线程饥饿。
7、什么是线程饥饿?
饥饿,一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。
Java 中导致饥饿的原因:
- 高优先级线程吞噬所有的低优先级线程的 CPU 时间。
- 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
- 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法),因为其他线程总是被持续地获得唤醒。
8、你对线程优先级的理解是什么?
每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。
- 我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个 int 变量(从1-10),1 代表最低优先级,10 代表最高优先级。
- Java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关,如非特别需要,一般无需设置线程优先级。
9、线程的生命周期?
线程一共有五个状态,分别如下:
- 新建(new):当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。例如:
Thread t1 = new Thread()
。 -
可运行(runnable):线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start 方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权。例如:
t1.start()
。 -
运行(running):线程获得 CPU 资源正在执行任务(
#run()
方法),此时除非此线程自动放弃 CPU 资源或者有优先级更高的线程进入,线程将一直运行到结束。 - 死亡(dead):当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
- 自然终止:正常运行完
#run()
方法,终止。 - 异常终止:调用
#stop()
方法,让一个线程终止运行。
- 自然终止:正常运行完
-
堵塞(blocked):由于某种原因导致正在运行的线程让出 CPU 并暂停自己的执行,即进入堵塞状态。直到线程进入可运行(runnable)状态,才有机会再次获得 CPU 资源,转到运行(running)状态。阻塞的情况有三种:
-
正在睡眠:调用
#sleep(long t)
方法,可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入可运行(runnable)状态。
-
正在等待:调用
#wait()
方法。调用
notify()
方法,回到就绪状态。 -
被另一个线程所阻塞:调用
#suspend()
方法。调用
#resume()
方法,就可以恢复。
-
整体如下图所示:
Thread 的线程状态
- 中间一行是线程的顺畅的执行过程的四个状态。其上下两侧,是存在对应的情况,达到阻塞状态和恢复执行的过程。
- 有一点要注意,新建(new)和死亡(dead)是单向的状态,不可重复。**
- 理解线程的状态,可以用早起坐地铁来比喻这个过程:
- 还没起床:sleeping 。
- 起床收拾好了,随时可以坐地铁出发:Runnable 。
- 等地铁来:Waiting 。
- 地铁来了,但要排队上地铁:I/O 阻塞 。
- 上了地铁,发现暂时没座位:synchronized 阻塞。
- 地铁上找到座位:Running 。
- 到达目的地:Dead 。
如下是另外一个图,把阻塞的情况,放在了一起,也可以作为参考:
Thread 的线程状态
无意中,又看到一张画的更牛逼的,如下图:
Thread 的线程状态
如何结束一个一直运行的线程?
一般来说,有两种方式:
-
方式一,使用退出标志,这个 flag 变量要多线程可见。
在这种方式中,之所以引入共享变量,是因为该变量可以被多个执行相同任务的线程用来作为是否中断的信号,通知中断线程的执行。
-
方式二,使用 interrupt 方法,结合 isInterrupted 方法一起使用。
如果一个线程由于等待某些事件的发生而被阻塞,又该怎样停止该线程呢?这种情况经常会发生,比如当一个线程由于需要等候键盘输入而被阻塞,或者调用
Thread#join()
方法,或者Thread#sleep(...)
方法,在网络中调用ServerSocket#accept()
方法,或者调用了DatagramSocket#receive()
方法时,都有可能导致线程阻塞,使线程处于处于不可运行状态时。即使主程序中将该线程的共享变量设置为true
,但该线程此时根本无法检查循环标志,当然也就无法立即中断。这里我们给出的建议是,不要使用
Thread#stop()· 方法,而是使用 Thread 提供的
#interrupt()` 方法,因为该方法虽然不会中断一个正在运行的线程,但是它可以使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态,退出堵塞代码。
所以,方式一和方式二,并不是冲突的两种方式,而是可能根据实际场景下,进行结合。
一个线程如果出现了运行时异常会怎么样?
如果这个异常没有被捕获的话,这个线程就停止执行了。
另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放。
10、创建线程的方式及实现?
Java 中创建线程主要有三种方式:
- 方式一,继承 Thread 类创建线程类。
- 方式二,通过 Runnable 接口创建线程类。
- 方式三,通过 Callable 和 Future 创建线程。
10.1、创建线程的三种方式的对比
- 使用方式一
- 优点:编写简单,如果需要访问当前线程,则无需使用
Thread#currentThread()
方法,直接使用this
即可获得当前线程。 - 缺点:线程类已经继承了 Thread 类,所以不能再继承其他父类。
- 优点:编写简单,如果需要访问当前线程,则无需使用
-
使用方式二、或方式三
优点:【最重要】可以使用线程池。-
在这种方式下,多个线程可以共享同一个
target
对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。Runnable runner = new Runnable(){ ... }; // 通过new Thread(target, name) 方法创建新线程 new Thread(runna,"新线程1").start(); new Thread(runna,"新线程2").start();
- 当然,实际比较少这么用。
- 线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
-
缺点:编程稍微复杂,如果要访问当前线程,则必须使用Thread#currentThread()
方法。
11、start 和 run 方法有什么区别?
- 当你调用 start 方法时,你将创建新的线程,并且执行在 run 方法里的代码。
- 但是如果你直接调用 run 方法,它不会创建新的线程也不会执行调用线程的代码,只会把 run 方法当作普通方法去执行。
12、一个线程运行时发生异常会怎样?
如果异常没有被捕获该线程将会停止执行。
Thread.UncaughtExceptionHandler
是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候 JVM 会使用Thread#getUncaughtExceptionHandler()
方法来查询线程的 UncaughtExceptionHandler 并将线程和异常作为参数传递给 handler 的#uncaughtException(exception)
方法进行处理。
13、如何使用 wait + notify 实现通知机制?
在 Java 发展史上,曾经使用 suspend、resume 方法对于线程进行阻塞唤醒,但随之出现很多问题,比较典型的还是死锁问题。
解决方案可以使用以对象为目标的阻塞,即利用 Object 类的 wait 和 notify方法实现线程阻塞。
- 首先,wait、notify 方法是针对对象的,调用任意对象的 wait 方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify 方法则将随机解除该对象阻塞的线程,但它需要重新获取改对象的锁,直到获取成功才能往下执行。
- 其次,wait、notify 方法必须在
synchronized
块或方法中被调用,并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。
14、Thread类的 sleep 方法和对象的 wait 方法都可以让线程暂停执行,它们有什么区别?
- sleep 方法,是线程类 Thread 的静态方法。调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态)
- wait 方法,是 Object 类的方法。调用对象的
#wait()
方法,会导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的#notify()
方法(或#notifyAll()
方法)时,才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
15、请说出与线程同步以及线程调度相关的方法?
- wait 方法,使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁。
- sleep 方法,使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常。
- notify 方法,唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关。
- notityAll 方法,唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态。
16、notify 和 notifyAll 有什么区别?
当一个线程进入 wait 之后,就必须等其他线程 notify/notifyAll 。
- 使用 notifyAll,可以唤醒所有处于 wait 状态的线程,使其重新进入锁的争夺队列中,而 notify 只能唤醒一个。
- 如果没把握,建议 notifyAll ,防止 notify 因为信号丢失而造成程序错误。
17、为什么 wait, notify 和 notifyAll 这三方法不在 Thread 类里面?
一个很明显的原因是 Java 提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。
由于 wait,notify 和 notifyAll 方法都是锁级别的操作,所以把它们定义在 Object 类中,因为锁属于对象。
18、为什么 wait 和 notify 方法要在同步块中调用?
- Java API 强制要求这样做,如果你不这么做,你的代码会抛出 IllegalMonitorStateException 异常。
- 还有一个原因是为了避免 wait 和 notify 之间产生竞态条件。
19、为什么你应该在循环中检查等待条件?
处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
所以,我们不能写 if (condition)
而应该是 while (condition)
,特别是 CAS 竞争的时候。示例代码如下:
// The standard idiom for using the wait method synchronized (obj) {while (condition does not hold) {obj.wait(); // (Releases lock, and reacquires on wakeup)}... // Perform action appropriate to condition } |
20、sleep、join、yield 方法有什么区别?
20.1、sleep 方法
在指定的毫秒数内,让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有synchronized
同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常。
比如有两个线程同时执行(没有 synchronized
),一个线程优先级为MAX_PRIORITY
,另一个为 MIN_PRIORITY
。
- 如果没有 sleep 方法,只有高优先级的线程执行完成后,低优先级的线程才能执行。但当高优先级的线程
#sleep(5000)
后,低优先级就有机会执行了。- 总之,sleep 方法,可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
20.2、yield 方法
yield 方法和 sleep 方法类似,也不会释放“锁标志”,区别在于:
- 它没有参数,即 yield 方法只是使当前线程重新回到可执行状态,所以执行yield 的线程有可能在进入到可执行状态后马上又被执行。
- 另外 yield 方法只能使同优先级或者高优先级的线程得到执行机会,这也和 sleep 方法不同。
20.3、join 方法
Thread 的非静态方法 join ,让一个线程 B “加入”到另外一个线程 A 的尾部。在线程 A 执行完毕之前,线程 B 不能工作。示例代码如下:
Thread t = new MyThread(); t.start(); t.join(); |
- 保证当前线程停止执行,直到该线程所加入的线程
t
完成为止。然而,如果它加入的线程t
没有存活,则当前线程不需要停止。
21、线程的 sleep 方法和 yield 方法有什么区别?
- sleep 方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会。yield 方法只会给相同优先级或更高优先级的线程以运行的机会。
- 线程执行 sleep 方法后转入阻塞(blocked)状态,而执行 yield 方法后转入就绪(ready)状态。
- sleep 方法声明抛出 InterruptedException 异常,而 yield 方法没有声明任何异常。
- sleep 方法比 yield 方法(跟操作系统 CPU 调度相关)具有更好的可移植性。
22、为什么 Thread 类的 sleep 和 yield 方法是静态的?
Thread 类的 sleep 和 yield 方法,将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
23、sleep(0) 有什么用途?
Thread#sleep(0)
方法,并非是真的要线程挂起 0 毫秒,意义在于这次调用 Thread#sleep(0)
方法,把当前线程确实的被冻结了一下,让其他线程有机会优先执行。Thread#sleep(0)
方法,是你的线程暂时放弃 CPU ,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作。
24、你如何确保 main 方法所在的线程是 Java 程序最后结束的线程?
考点,就是 join 方法。
我们可以使用 Thread 类的 #join()
方法,来确保所有程序创建的线程在 main 方法退出前结束。
25、interrupted 和 isInterrupted 方法的区别?
25.1、interrupt 方法
Thread#interrupt()
方法,用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。
注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出 InterruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。
25.2、interrupted
Thread#interrupted()
静态方法,查询当前线程的中断状态,并且清除原状态。如果一个线程被中断了,第一次调用 #interrupted()
方法则返回 true
,第二次和后面的就返回 false
了。
// Thread.javapublic static boolean interrupted() {return currentThread().isInterrupted(true); // 清理 }private native boolean isInterrupted(boolean ClearInterrupted); |
25.3、interrupted
Thread#isInterrupted()
方法,查询指定线程的中断状态,不会清除原状态。代码如下:
// Thread.javapublic boolean isInterrupted() {return isInterrupted(false); // 不清楚 }private native boolean isInterrupted(boolean ClearInterrupted); |
26、什么叫线程安全?
线程安全,是编程中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
26.1、Servlet 是线程安全吗?
Servlet 不是线程安全的,Servlet 是单实例多线程的,当多个线程同时访问同一个方法,是不能保证共享变量的线程安全性的。
26.2、SpringMVC 是线程安全吗?
不是的,和 Servlet 类似的处理流程。
26.3、单例模式的线程安全性?
老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,我总结一下:
- 饿汉式单例模式的写法:线程安全
- 懒汉式单例模式的写法:非线程安全
- 双检锁单例模式的写法:线程安全
27、多线程同步和互斥有几种实现方法,都是什么?
27.1、线程同步
线程同步,是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
线程间的同步方法,大体可分为两类:用户模式和内核模式。顾名思义:
- 内核模式,就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态。内核模式下的方法有:
- 事件
- 信号量
- 互斥量
- 用户模式,就是不需要切换到内核态,只在用户态完成操作。用户模式下的方法有:
- 原子操作(例如一个单一的全局变量)
- 临界区
27.2、线程互斥
线程互斥,是指对于共享的进程系统资源,在各单个线程访问时的排它性。
- 当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。
- 线程互斥可以看成是一种特殊的线程同步。
28、如何在两个线程间共享数据?
在两个线程间共享变量,即可实现共享。
一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如果有对共享变量的复合操作,那么也得保证复合操作的线程安全性。
29、怎么检测一个线程是否拥有锁?
调用 Thread#holdsLock(Object obj)
静态方法,它返回 true
如果当且仅当当前线程拥有某个具体对象的锁。代码如下:
// Thread.javapublic static native boolean holdsLock(Object obj); |
30、10 个线程和 2 个线程的同步代码,哪个更容易写?
从写代码的角度来说,两者的复杂度是相同的,因为同步代码与线程数量是相互独立的。
但是同步策略的选择依赖于线程的数量,因为越多的线程意味着更大的竞争,所以你需要利用同步技术,如锁分离,这要求更复杂的代码和专业知识。
31、什么是 ThreadLocal 变量?
ThreadLocal ,是 Java 里一种特殊的变量。每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。
它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用 ThreadLocal 让 SimpleDateFormat 变成线程安全的,因为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。
- 首先,通过复用减少了代价高昂的对象的创建个数。
- 其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。
所以,ThreadLocal 很适合实现线程级的单例。
32、什么是 InheritableThreadLocal ?
InheritableThreadLocal 类,是 ThreadLocal 类的子类。ThreadLocal 中每个线程拥有它自己的值,与 ThreadLocal 不同的是,InheritableThreadLocal 允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。
在多线程环境下,SimpleDateFormat 是线程安全的吗?
不是,非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如将 SimpleDateFormat 限制在 ThreadLocal 中。
如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time 库。
33、如何在 Java 中获取线程堆栈?
-
kill -3 [java pid]
不会在当前终端输出,它会输出到代码执行的或指定的地方去。比如,
kill -3 tomcat pid
, 输出堆栈到 log 目录下。 -
Jstack [java pid]
这个比较简单,在当前终端显示,也可以重定向到指定文件中。
-
JVisualVM:Thread Dump
不做说明,打开 JVisualVM 后,都是界面操作,过程还是很简单的。
34、什么是Java Timer 类?
java.util.Timer
,是一个工具类,可以用于安排一个线程在未来的某个特定时间执行。Timer 类可以用安排一次性任务或者周期任务。
java.util.TimerTask
,是一个实现了 Runnable 接口的抽象类,我们需要去继承这个类来创建我们自己的定时任务并使用 Timer 去安排它的执行。
目前有开源的 Qurtz 可以用来创建定时任务。
35、你有哪些多线程开发良好的实践?
-
1、给线程命名。
这样可以方便找 bug 或追踪。OrderProcessor、QuoteProcessor、TradeProcessor 这种名字比 Thread-1、Thread-2、Thread-3 好多了,给线程起一个和它要完成的任务相关的名字,所有的主要框架甚至JDK都遵循这个最佳实践。
-
2、最小化同步范围。
锁花费的代价高昂且上下文切换更耗费时间空间,试试最低限度的使用同步和锁,缩小临界区。因此相对于同步方法我更喜欢同步块,它给我拥有对锁的绝对控制权。
-
3、优先使用
volatile
,而不是synchronized
。 -
4、尽可能使用更高层次的并发工具而非 wait 和 notify 方法来实现线程通信。
首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 这些同步类简化了编码操作,而用 wait 和 notify 很难实现对复杂控制流的控制。
其次,这些类是由最好的企业编写和维护在后续的 JDK 中它们还会不断优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
-
5、优先使用并发容器,而非同步容器。
这是另外一个容易遵循且受益巨大的最佳实践,并发容器比同步容器的可扩展性更好,所以在并发编程时使用并发集合效果更好。如果下一次你需要用到 Map ,我们应该首先想到用 ConcurrentHashMap 类。
-
6、考虑使用线程池。
36、并发编程和并行编程有什么区别?
并发(Concurrency)和并行(Parallellism)是:
- 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
- 解释二:并行是在不同实体上的多个事件;并发是在同一实体上的多个事件。
- 解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如 Hadoop 分布式集群。
所以并发编程的目标是,充分的利用处理器的每一个核,以达到最高的处理性能。
37、同步和异步有何异同,在什么情况下分别使用他们?
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
当然,如果我们对效率没有特别大的要求,也不一定需要使用异步编程,因为它会带来编码的复杂性。总之,合适才是正确的。
总结
本篇文章梳理了java并发相关高频的面试题,希望对你有帮助哦。
JAVA面试题传送门:
Java面试题之集合篇
相关文章:
Java面试之并发篇(一)
1、前言 本篇主要总结JAVA面试中关于并发相关的高频面试题。本篇的面试题基于网络整理,和自己编辑。在不断的完善补充哦。 2、简述程序、进程、线程、的基本概念? 2.1、程序 程序,是含有指令和数据的文件,被存储在磁盘或其他的…...
分布式全局id
分布式全局id snowflake 算法是 twitter 开源的分布式 id 生成算法,采用 Scala 语言实现,是把一个 64 位的 long 型的 id,1 个 bit 是不用的,用其中的 41 bits 作为毫秒数,用 10 bits 作为工作机器 id,12 …...
springboot 房屋租赁系统
spring boot mysql mybatis 前台后端...
TypeScript接口、对象
目录 1、TypeScript 接口 1.1、实例 1.2、联合类型和接口 1.3、接口和数组 1.4、接口和继承 1.5、单继承实例 1.6、多继承实例 2、TypeScript 对象 2.2、对象实例 2.3、TypeScript类型模板 2.4、鸭子类型(Duck typing) 1、TypeScript 接口 接口…...
Flask 菜品管理
common/libs/Helper.py getDictFilterField() 方法 用于在web/templates/food/index.html中展示菜品分类 如何能够通过food里面的cat_id获取分类信息呢?只能通过for循环,这样会很麻烦,所以定义了这个方法。 这个方法可以的查询返回结果…...
亚马逊实时 AI 编程助手 CodeWhisperer使用体验
文章目录 1:什么是CodeWhisperer ?2:试用3:上手体验 1:什么是CodeWhisperer ? 最近ChatGPT展现出强大AI能力给我们带来了深刻的影响,AI现在不是一个概念,基于AI的产品一定在各行各业…...
[机缘参悟-123] :实修 - 东西方各种思想流派实修的要旨与比较?
目录 前言: 一、东方各种思想流派实修的要旨? 1.1 儒、释、道、法的主要思想 1.2 儒、释、道、法各种追求的目标 1.3 儒、释、道、法各自修行的法门或修行的途径 二、西方灵修的各种派别的要旨? 2.0 西方灵修的各种派别 2.1 玛雅星系…...
基于51单片机的数字时钟系统设计
标题:基于51单片机的数字时钟系统设计与实现 摘要: 本文详细介绍了基于STC89C51单片机设计一款具有精确计时功能的数字时钟系统的全过程。该系统利用了单片机内部的定时器/计数器资源,结合液晶显示屏和按键输入模块,实现了时间显…...
《每天十分钟》-红宝书第4版-基本引用类型
引用值(或者对象)是某个特定引用类型的实例。在 ECMAScript 中,引用类型是把数据和功能组织到一起的结构,经常被人错误地称作“类”。虽然从技术上JavaScript 是一门面向对象语言,但ECMAScript 缺少传统的面向对象编程…...
【EAI 005】EmbodiedGPT:通过具身思维链进行视觉语言预训练的具身智能大模型
论文描述:EmbodiedGPT: Vision-Language Pre-Training via Embodied Chain of Thought 论文作者:Yao Mu, Qinglong Zhang, Mengkang Hu, Wenhai Wang, Mingyu Ding, Jun Jin, Bin Wang, Jifeng Dai, Yu Qiao, Ping Luo 作者单位:The Universi…...
一文读懂「Chain of Thought,CoT」思维链
前言: 思维链,在人工智能领域,是一个非常非常新的概念。强大的逻辑推理是大语言模型“智能涌现”出的核心能力之一,好像AI有了人的意识一样。而推理能力的关键在于——思维链(Chain of Thought,CoT)。 相关概念: 语言智能可以被理解为“使用基于自然语言的概念对经验事…...
杨中科 ASP.NET Core 中的依赖注入的使用
ASP.NET CORE中服务注入的地方 1、在ASP.NET Core项目中一般不需要自己创建ServiceCollection、IServiceProvider。在Program.cs的builder.Build()之前向builderServices中注入 2、在Controller中可以通过构造方法注入服 务。 3、演示 新建一个calculator类 注入 新建TestC…...
Spring Boot 和 Spring 有什么区别
Spring Boot 和 Spring 是两个不同的概念,它们服务于不同的目的,但它们之间有着紧密的联系。下面是它们之间的主要区别: 目的和定位: Spring:Spring 是一个开源的 Java 平台,它最初由 Rod Johnson 创建&am…...
Linux——以太网
一、Linux下的以太网架构 1、Linux 系统网络协议层架构 PHY 驱动的功能处于链路层: 2、以太网物理层与硬件连接 我们重点关注以下两点: (1)与 MAC 设备的接口,即是 gmii 还是 rgmii。 (2) Phy…...
HTTP 代理原理及实现(二)
在上篇《HTTP 代理原理及实现(一)》里,我介绍了 HTTP 代理的两种形式,并用 Node.js 实现了一个可用的普通 / 隧道代理。普通代理可以用来承载 HTTP 流量;隧道代理可以用来承载任何 TCP 流量,包括 HTTP 和 H…...
JavaScript 地址信息与页面跳转
在JavaScript中,你可以使用各种方法来处理地址信息并进行页面跳转。以下是一些常见的方法: 1.使用window.location对象: window.location对象包含了当前窗口的URL信息,并且可以用来进行页面跳转。 * 获取URL的某一部分…...
力扣(leetcode)第383题赎金信(Python)
383.赎金信 题目链接:383.赎金信 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以,返回 true ;否则返回 false 。 magazine 中的每个字符只能在 ransomNote…...
提升网络安全重要要素IP地址
在数字化时代,网络安全已经成为人们关注的焦点。本文将深入探讨网络安全与IP地址之间的紧密联系,以及IP地址在构建数字世界的前沿堡垒中的关键作用。 网络安全是当今数字社会中不可忽视的挑战之一。而IP地址,作为互联网通信的基础协议&#…...
解析c++空指针解引用奔溃
空指针解引用引起程序奔溃是c/c中最常见的稳定性错误之一。 显然并非所有使用空指针的语句都会导致奔溃,那什么情况下使用空指针才会引起程序奔溃呢?有一个判断标准:判断空指针是否会导致访问非法内存的情况,如果会导致访问非法内…...
Oracle START WITH 递归语句的使用方法及示例
Oracle数据库中的START WITH语句经常与CONNECT BY子句一起使用,以实现对层次型数据的查询。这种查询模式非常适用于处理具有父子关系的数据,如组织结构、分类信息等。 理解START WITH和CONNECT BY 在层次型查询中,START WITH定义了层次结构…...
使用Windbg动态调试目标进程的一般步骤详解
目录 1、概述 2、将Windbg附加到已经启动起来的目标进程上,或者用Windbg启动目标程序 2.1、将Windbg附加到已经启动起来的目标进程上 2.2、用Windbg启动目标程序 2.3、Windbg关联到目标进程上会中断下来,输入g命令将该中断跳过去 3、分析实例说明 …...
Linux驱动学习—输入子系统
1、什么是输入子系统? 输入子系统是Linux专门做的一套框架来处理输入事件的,像鼠标,键盘,触摸屏这些都是输入设备,但是这邪恶输入设备的类型又都不是一样的,所以为了统一这些输入设备驱动标准应运而生的。…...
计算机网络(2)
计算机网络(2) 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU) 计算机网络和因特网(2)分组交换网中的时延、丢包和吞吐量时延丢包吞吐量总结 协议层次及其服务模型模型类型OSI模型分析TCP/IP模型分析 追溯历史 小程一言 我…...
什么是预训练Pre-training—— AIGC必备知识点,您get了吗?
Look!👀我们的大模型商业化落地产品📖更多AI资讯请👉🏾关注Free三天集训营助教在线为您火热答疑👩🏼🏫 随着人工智能(AI)不断重塑我们的世界,其发展的一个关键方面已经…...
bat脚本sqlserver 不同数据库同步
如果你想使用批处理脚本(.bat)在 SQL Server 中同步不同数据库的数据,你可以考虑以下步骤: 设置环境变量: 确保你的系统环境变量中已经设置了 SQLCMD 和 BCP 的路径。 编写批处理脚本: 使用 sqlcmd 来执行…...
阶段十-分布式-Redis02
第一章 Redis 事务 1.1 节 数据库事务复习 数据库事务的四大特性 A:Atomic ,原子性,将所以SQL作为原子工作单元执行,要么全部执行,要么全部不执行;C:Consistent,一致性࿰…...
微信小程序实战-02翻页时钟-2
微信小程序实战系列 《微信小程序实战-01翻页时钟-1》 文章目录 微信小程序实战系列前言计时功能实现clock.wxmlclock.wxssclock.js 运行效果总结 前言 接着《微信小程序实战-01翻页时钟-1》,继续完成“6个页面的静态渲染和计时”功能。 计时功能实现 clock.wxm…...
每天刷两道题——第十一天
1.1滑动窗口最大值 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。 输入:nums [1,3,-1,-3,5,3,6,7], k 3 输出&…...
Git提交规范
一. 修改类型 每个类型值都表示了不同的含义,类型值必须是以下的其中一个: feat:提交新功能fix:修复了bugdocs:只修改了文档style:调整代码格式,未修改代码逻辑(比如修改空格、格式…...
apache2的虚拟主机的配置
APACHE2的虚拟主机配置 本章中心概括: 虚拟web主机的初步认识,在redhat系列系统中如何配置,在Debian系列系统中如何配置。 什么是apache2虚拟主机: 简单点讲,就是在同一个物理机中配置多个虚拟主机,从而达…...
做平台网站怎么做的/建站abc
实现以下的add()的方法 output()时打印前面的参数之和 add(1,2).add(3).add(4).output()function add(...args){return [...args] } Array.prototype.add function(...args){this.push(...args)return this } Array.prototype.output function(){return this.reduce((a,b)&g…...
开发公司成本管理/泰安seo培训
要是想写个烂代码,我们只需遵守这十九条准则?「代码写得好」是对机器学习研究者及开发者最好的赞扬。其第一层意思是说,你的模型非常好,有自己的理解与修正;第二层意思是说代码的结构、命名规则、编写逻辑都非常优秀。…...
个人网页网站建设/网络营销做得比较成功的案例
1. 场景大家如果平常遇到不认识的英文,相信大部分的人都会复制内容后,使用翻译软件,或者拷贝到网站上去执行翻译。当然,对于 IDE、浏览器可以装一些插件来翻译,有道也有划词翻译。但是,经常会有一些解析对话…...
网站开发的系统测试/广告推广 精准引流
问题详细描述假设,我已经在我的本地系统中保存了一个JSON文件,同时创建了一个Javascript文件,以便读取JSON文件并打印数据。JSON文件如下:{"resource":"A","literals":["B","C"…...
aisex wordpress/b2b免费发布信息网站
Docker通过dockerfile创建镜像时,RUN和CMD是相当重要的命令,本文对这CMD指令进行解析。 CMD 语法 # exec形式,这是首选形式. CMD ["executable","param1","param2"] # 提供给ENTRYPOINT的默认参数. CMD [&quo…...
广州专门做网站/seo推广排名公司
.col-lg-*: //*:代表列数,一行12列 //表示在屏幕大于等于≥1200px时按照这个规律展示 如: .col-lg-2 //表示在屏幕大于等于≥1200px时占2列而其他的按照表中的信息对照即可 也可以自己去bootstrap官网查找: 网址:https://v3.bootcss.com/cs…...