- 921. Minimum Add to Make Parentheses Valid 使括号有效的最少添加
- 915. Partition Array into Disjoint Intervals 分割数组
- 932. Beautiful Array 漂亮数组
- 940. Distinct Subsequences II 不同的子序列 II
应聘Java 岗,总是免不了几个 Java 并发编程的面试题,不过大多数都局限在 java.util.concurrent 包下的知识和实现问题。本文针对 Java 并发相关的常见的面试题做一些解释。
这是一个非常基础的面试题,如果这道题没有回答的比较满意,一般情况下,面试官会认为应聘者在并发方面的基础只是不牢固,就不会继续深入询问其它并发问题了。
1、 进程和线程都是并发单元,但它们有一个根本区别:** 进程不共享公共内存,而线程则共享**;
2、 从操作系统的角度来看,进程是一个独立的软件,在其自己的虚拟内存空间中运行任何一个多任务操作系统(这几乎意味着任何现代操作系统)都必须将内存中的进程分开,这样一个失败的进程就不会通过加扰公共内存来拖累所有其它进程因此,进程通常是隔离的,它们通过进程间通信进行协作,进程间通信由操作系统定义为一种中间API;
3、 相反,线程是应用程序的一部分,它与同一应用程序的其他线程共享公共内存使用公共内存可以减少大量开销,因此使用线程可以更快的交换数据和进行线程间协作;
关于进程间通讯那一块可以不用回答,如果你不懂的话,不必然会导致接下来的某个问题是 进程间通讯的的原理.
这道题考察的是对 Runnable 的理解。
创建一个线程的实例,有两种方法可供选择:
1、 把Runnable的实例传递给Thread的构造函数并调用start()方法;
Thread thread1 = new Thread(() ->
System.out.println("Hello World from Runnable!"));
thread1.start();
Runnable是一个函数接口,因此可以作为 lambda 表达式传递
2、 因为线程本身也实现了Runnable接口,所以另一种创建线程的方法是创建一个匿名子类,覆写它的run()方法,然后调用start();
Thread thread2 = new Thread() {
@Override
public void run() {
System.out.println("Hello World from subclass!");
}
};
thread2.start();
这道题考察的是对线程生命周期的理解。
1、 一般情况下,我们会使用Thread.getState()方法检查线程(Thread)的状态;
2、 线程的不同状态都定义在Thread.State枚举中;
3、 线程的所有状态如下所示;
1、 NEW:一个尚未调用Thread.start()方法启动的新Thread实例;
2、 RUNNABLE:一个正在运行的线程它被称为runnable,因为在任何给定时间,它要么正在运行要么在等待线程调度当调用Thread.start()方法时,会将一个NEW线程进入RUNNABLE状态;
3、 BLOCKED:如果正在运行的线程需要进入同步部分但由于另一个线程持有此部分的监视器而无法执行此操作,则该线程将被阻塞;
4、 WAITING:如果线程等待另一个线程执行特定操作,则该线程进入此状态例如,一个线程在它持有的监视器上调用Object.wait()法时进入此状态,或者在另一个线程上调用Thread.join()方法也会进入此状态;
5、 IMED_WAITING:跟WAITING状态差不多但线程在调用Thread.sleep()、Object.wait()、或Thread.join()和其他一些方法的定时版本后进入此状态;
6、 TERMINATED:当一个线程已经完成它的Runnable.run()方法的执行并终止时进入此状态;
1、 Runnable接口表示必须在单独的线程中运行的计算单位,它只有一个run()方法Runnable接口不允许此方法返回值或抛出未经检查的异常;
2、 Callable接口表示具有返回值的任务,它只有一个call()方法call()方法可以返回一个值(可以是Void),也可以抛出一个异常Callable通常在ExecutorService实例中用于启动异步任务,然后调用返回的Future实例以获取其值;
1、 守护线程是一个不阻止Java虚拟机(JVM)退出的线程当所有非守护线程终止时,JVM只是放弃所有剩余的守护线程;
2、 守护线程通常用于为其他线程执行一些支持或服务任务,但我们应该考虑到它们可能随时被放弃;
3、 要将一个线程作为守护线程启动,应该在调用start()之前使用setDaemon()方法设置为守护线程如下所示;
Thread daemon = new Thread(()
-> System.out.println("Hello from daemon!"));
daemon.setDaemon(true);
daemon.start();
奇怪的是,如果将上面的代码放在 main() 内运行,则可能无法打印该消息。而发生这种情况的原因,是因为 main() 线程在守护线程运行到打印消息之前就已经终止。
我们不应该在守护线程中执行任何 I/O 操作,因为它们甚至无法执行其 finally 块并在被放弃时关闭资源。
1、 中断(interrupt)标志或中断状态是线程中断时设置的内部线程标志(flag属性);
2、 要设置一个线程的中断标志,只需要简单的在线程对象上调用thread.interrupt()方法;
3、 如果在某个方法内部的一个线程抛出了InterruptedException(wait、join、sleep等),那么此方法会立即抛出InterruptedException线程可以根据自己的逻辑自由处理此异常如果一个线程不在这样的方法中并且调用了thread.interrupt(),则不会发生任何特殊情况;
4、 线程的中断状态可以通过使用静态Thread.interrupted()方法或实例的isInterrupted()方法定期检查这两个方法的区别是静态Thread.interrupt()会清除了中断标志,而isInterrupted()则不会;
1、 Executor和ExecutorService是java.util.concurrent框架提供的两个相关接口;
2、 Executor是一个非常简单的接口,只有一个execute()方法接受Runnable实例来执行在大多数情况下,这是我们的任务执行代码应该依赖的接口;
3、 ExecutorService扩展了Executor接口,并且添加了许多其它方法以处理和检查并发任务执行服务的生命周期(在关闭时终止任务)和更复杂的异步任务处理,包括Futures;
更多 Executor 和 ExecutorService 的知识,可以访问 一文秒懂 Java ExecutorService。
这是一个非常变 tai 的问题。问这个问题的面试官,你想咋样啊 ?
ExecutorService 接口有三个标准实现
1、 ThreadPoolExecutor:使用线程池执行任务一旦某个线程完成执行任务,它就会回到线程池中如果池中的所有线程都忙,则任务必须等待轮到它;
2、 ScheduledThreadPoolExecutor:允许安排任务执行,而不是简单的在线程可用时立即运行任务它还可以按固定频率或固定延迟安排任务;
3、 ForkJoinPool:是一个特殊的ExecutorService,用于处理递归算法任务如果你使用常规ThreadPoolExecutor进行递归算法,那么你很快发现所有线程都在忙着等待较低级别的递归完成ForkJoinPool实现了所谓的工作窃取算法,允许它更有效地使用可用线程;
Java 内存模式是 Java 语言规范的一部分,在 [第 17.4 章][17.4] 中描述。
JMM规定了多个线程如何访问并发 Java 应用程序中的公共内存,以及一个线程的数据更改如何对其他线程可见。
是不是很简单,虽然简短又简洁,但如果没有强大的数学背景,JMM 可能很难掌握。
对内存模型的需求源于这样一个事实:** Java 代码访问数据的方式并不像它在底层实际发生的那样**。
在保证内存读写的可观察结果是相同的情况下,Java 编译器,JIT 编译器甚至 CPU 都可以对内存读写进行重新排序或优化。
当我们的应用程序扩展到多个线程时,这会导致反直觉的结果,因为大多数这些优化只会考虑单个执行线程( 跨线程优化器仍然非常难以实现 )。
另一个可怕的问题是现代系统中的内存是多层的:** 处理器的多个内核可能会在其缓存或读/写缓冲区中保留一些非刷新数据,这也会影响从其它内核观察到的内存状态**。
更糟糕的是,不同内存访问架构的存在将打破Java 「 一次编写,随处运行 」 的承诺。
但另所有 Java 程序员高兴的是,JMM 指定了在设计多线程应用程序时可能依赖的一些保证。坚持这些保证有助于程序员编写在各种体系结构之间稳定且可移植的多线程代码。
JMM的主要概念是:
对于给定的程序,我们可以观察到具有各种结果的多个不同的执行.但是如果一个程序正确同步,那么它的所有执行似乎都是顺序一致的,这意味着我们可以将多线程程序推断为一系列按顺序发生的动作。这样可以省去考虑引擎盖下重新排序,优化或数据缓存的麻烦。
如果你了解协程,相关的概念和协程很相像的。
根据Java 内存模型 ( 参见 Q9 ) ,volatile 字段具有特殊属性。volatile 变量的读取和写入是同步操作,这意味着它们具有总排序( 所有线程将遵循这些操作的一致顺序 )。根据此顺序,保证读取 volatile 变量可以观察到对此变量的最后一次写入。
如果你有一个从多个线程访问的字段,且至少有一个线程写入它,那么你应该考虑使它变得 volatile ,否则某个线程从这个字段读取的内容并不会得到一丝的保证。
volatile 的另一个保证是写入和读取 64 位值( long 类型和 double 类型 )的原子性。如果没有 volatile 修饰符,读取此类字段可能会观察到另一个线程部分写入的值。
是不是瞬间蒙了?我们来解释一下
1、 对int类型(32位)变量的写入保证是原子的,无论它是否是易失性的;
2、 long类型(64位)变量可能需要在两个单独的步骤中写入,例如,在32位体系结构上,因此默认情况下,没有原子性保证但是,如果添加了volatile修饰符,则保证以原子方式访问long变量;
3、 递增操作通常由多个步骤完成(检索值,更改它并写回),因此它永远不会保证是原子的,变量是易变的如果要实现值的原子增量,则应使用类AtomicInteger,AtomicLong等;
JVM基本上会保证在任何线程获取对象之前初始化类的 final 字段。
如果没有这种保证,由于重新排序或其他优化,在初始化该对象的所有字段之前,可以向另一个线程发布对象的引用,即变得可见。这可能会导致对这些字段的访问。
这就是为什么在创建不可变对象时,应始终将其所有字段设为 final,即使它们不能通过 getter 方法访问。
块(block ) 之前的 synchronized 关键字表示进入该块的任何线程都必须获取监视器( 括号中的对象 )。
synchronized(object) {
// ...
}
如果监视器已被另一个线程获取,则前一个线程将进入 BLOCKED 状态并等待监视器被释放。
同步实例方法具有相同的语义,但会使用实例本身充当监视器。
```java
synchronized void instanceMethod() {
// ...
}
对于静态同步方法,监视器是表示声明类的 Class 对象。
static synchronized void staticMethod() {
// ...
}
如果方法是实例方法,则实例充当方法的监视器。在不同实例上调用该方法的两个线程获取不同的监视器,因此它们都不会被阻塞。
如果方法是静态的,则监视器是 Class 对象。对于两个线程,监视器是相同的,因此其中一个可能会阻塞并等待另一个退出 synchronized 方法。
拥有对象监视器的线程( 例如,已进入由对象保护的同步部分的线程 )可以调用 object.wait() 来临时释放监视器并为其他线程提供获取监视器的机会。例如,这可以在等待某个条件的情况下完成。
当另一个获取监视器的线程满足条件时,它可以调用 object.notify() 或 object.notifyAll() 并释放监视器。notify() 方法唤醒处于等待状态的单个线程,notifyAll() 方法唤醒等待此监视器的所有线程,并且它们都竞争重新获取锁定。
下面的BlockingQueue 实现演示了多个线程如何通过 wait-notify 模式一起工作。如果我们将一个元素放入一个空队列,那么在 take() 方法中等待的所有线程都会唤醒并尝试接收该值。如果我们将一个元素放入一个已经满了的队列,put() 方法将等待对 get() 方法的调用。get() 方法删除一个元素,并通知在 put() 方法中等待队列对新项目有空位置的线程。
public class BlockingQueue<T> {
private List<T> queue = new LinkedList<T>();
private int limit = 10;
public synchronized void put(T item) {
while (queue.size() == limit) {
try {
wait();
} catch (InterruptedException e) {}
}
if (queue.isEmpty()) {
notifyAll();
}
queue.add(item);
}
public synchronized T take() throws InterruptedException {
while (queue.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {}
}
if (queue.size() == limit) {
notifyAll();
}
return queue.remove(0);
}
}
fork/join 框架允许并行化递归算法。使用 ThreadPoolExecutor 之类的并行递归的主要问题是,可能会快速耗尽线程,因为每个递归步骤都需要自己的线程,而堆栈中的线程将处于空闲状态并等待。
fork/join 框架入口点是 ForkJoinPool 类,它是 ExecutorService 的一个实现。它实现了工作窃取算法,空闲线程会试图从忙线程中 「 窃取 」 工作。这允许在不同线程之间传播计算并在使用比通常的线程池所需的更少的线程时取得进展
我正在尝试在多线程环境中实现某种累积逻辑;我想知道没有 lock 和 synchronized 关键字是否有更好/更快的方法来做到这一点?以下是我当前的代码: public class Concurr
我需要帮助构建一个实现信号量的监视器,简单的 C 示例就可以。 这是为了证明可以在任何可以使用信号量的地方使用监视器。 最佳答案 如果您说允许使用互斥锁/condvars,请检查: #include
我已经构建了一些返回部分产品目录的 ajax,并且我正在尝试将 xml 输出到文档中,到目前为止,这是我所拥有的: $("#catalog").append("Item NamePriceDe
很抱歉,如果我的问题之前已经被问过,或者它太明显了,但我真的需要澄清这一点。感谢您的帮助。 在多用户界面中,如果来自不同用户的相同事务同时到达服务器,会发生什么? 我有下一张表: create tab
这可能是一个愚蠢的问题,但是这个程序的输出(它的方式)可以为零吗? public class Test2{ int a = 0; AtomicInteger b = new Atomi
假设我本地主机上的一个网站处理每个请求大约需要 3 秒。这很好,正如预期的那样(因为它在幕后进行了一些奇特的网络)。 但是,如果我在选项卡(在 firefox 中)中打开相同的 url,然后同时重新加
我对 MongoDB 的读锁定有点困惑。单个集合可以支持多少个并发读取操作? 最佳答案 如 tk 给出的链接中所写:http://www.mongodb.org/pages/viewpage.acti
如果有四个并发的 CUDA 应用程序在一个 GPU 中竞争资源会发生什么这样他们就可以将工作卸载到图形卡上了? Cuda Programming Guide 3.1 提到那里 某些方法是异步的: 内核
👊上次的百度面试遇到了关于spark的并发数的问题,今天我们就来将这些问题都一并解决一下,图画的的有点丑,还行大家见谅,百度实习的问题我放在了下面的链接👇: 链接: 2022百度大数据开发工程师实
我对 Groovy 线程有疑问。 我的任务是以某种方式翻译给定目录中的每个文件 并将生成的输出放在其他目录中的文件中。 我编写了以下代码,该代码有效: static def translateDir(
Java中的同步和锁定有什么区别? 最佳答案 synchronized是语言关键字;锁是对象。 当一个方法或代码块被标记为同步时,您是说该方法或代码块必须先获得某个锁对象(可以在同步的语法中指定)才能
我需要创建一个能够同时处理来自客户端的多个请求的并发 RPC 服务器。 使用 rpcgen linux编译器(基于sun RPC),不支持-A为并发服务器创建 stub 的选项。 (-A 选项在 so
System.out.println("Enter the number of what you would like to do"); System.out.println("1 = Manuall
我正在将我的应用程序移植到 iOS 8.0 并注意到 UIAlertView 已被弃用。 所以我改变了使用 UIAlertController 的方法。这在大多数情况下都有效。 除了,当我的应用程序打
我正在逐行同时读取两个文本文件。 我特别想做的是当lineCount在每个线程上都是相同的我想看看扫描仪当前正在读取的字符串。 我环顾四周寻找可以实现的某些模式,例如 Compare and Swap
我正在阅读 Java Concurrency in Practice .在章节中断政策部分 取消和关闭 它提到 A task should not assume anything about the
我正在尝试学习线程,互斥等的基础知识。遵循here的文档和示例。在下面的代码中,我得到预期的输出。问题: 想确认我是否有任何陷阱?我们如何改善下面的代码? 我的线程在哪一行尝试获取互斥锁或正在等待互斥
并发是指两个任务在不同的线程上并行运行。但是,异步方法并行运行,但在同一个线程上。这是如何实现的?另外,并行性怎么样? 这三个概念有什么区别? 最佳答案 并发和并行实际上与您正确推测的原理相同,两者都
以此ConcurrentDouble类定义为例: public class ConcurrentDouble { public double num = 0; public void subt
在得知并发确实增加了许多人的吞吐量后,我一直计划在项目中使用并发。现在我在多线程或并发方面还没有做太多工作,因此决定在实际项目中使用它之前学习并进行简单的概念验证。 以下是我尝试过的两个示例: 1.
我是一名优秀的程序员,十分优秀!