- 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 的一个实现。它实现了工作窃取算法,空闲线程会试图从忙线程中 「 窃取 」 工作。这允许在不同线程之间传播计算并在使用比通常的线程池所需的更少的线程时取得进展
我正在尝试打印 timeval 类型的值。实际上我可以打印它,但我收到以下警告: 该行有多个标记 格式“%ld”需要“long int”类型,但参数 2 的类型为“struct timeval” 程序
我正在编写自己的 unix 终端,但在执行命令时遇到问题: 首先,我获取用户输入并将其存储到缓冲区中,然后我将单词分开并将它们存储到我的 argv[] 数组中。IE命令是“firefox”以启动存储在
我是 CUDA 的新手。我有一个关于一个简单程序的问题,希望有人能注意到我的错误。 __global__ void ADD(float* A, float* B, float* C) { con
我有一个关于 C 语言 CGI 编程的一般性问题。 我使用嵌入式 Web 服务器来处理 Web 界面。为此,我在服务器中存储了一个 HTML 文件。在此 HTML 文件中包含 JavaScript 和
**摘要:**在代码的世界中,是存在很多艺术般的写法,这可能也是部分程序员追求编程这项事业的内在动力。 本文分享自华为云社区《【云驻共创】用4种代码中的艺术试图唤回你对编程的兴趣》,作者: break
我有一个函数,它的任务是在父对象中创建一个变量。我想要的是让函数在调用它的级别创建变量。 createVariable testFunc() [1] "test" > testFunc2() [1]
以下代码用于将多个连续的空格替换为1个空格。虽然我设法做到了,但我对花括号的使用感到困惑。 这个实际上运行良好: #include #include int main() { int ch, la
我正在尝试将文件写入磁盘,然后自动重新编译。不幸的是,某事似乎不起作用,我收到一条我还不明白的错误消息(我是 C 初学者 :-)。如果我手动编译生成的 hello.c,一切正常吗?! #include
如何将指针值传递给结构数组; 例如,在 txt 上我有这个: John Doe;xxxx@hotmail.com;214425532; 我的代码: typedef struct Person{
我尝试编写一些代码来检索 objectID,结果是 2B-06-01-04-01-82-31-01-03-01-01 . 这个值不正确吗? // Send a SysObjectId SNMP req
您好,提前感谢您的帮助, (请注意评论部分以获得更多见解:即,以下示例中的成本列已添加到此问题中;西蒙提供了一个很好的答案,但成本列本身并未出现在他的数据响应中,尽管他提供的功能与成本列一起使用) 我
我想知道是否有人能够提出一些解决非线性优化问题的软件包的方法,而非线性优化问题可以为优化解决方案提供整数变量?问题是使具有相等约束的函数最小化,该函数受某些上下边界约束的约束。 我已经在R中使用了'n
我是 R 编程的初学者,正在尝试向具有 50 列的矩阵添加一个额外的列。这个新列将是该行中前 10 个值的平均值。 randomMatrix <- generateMatrix(1,5000,100,
我在《K&R II C 编程 ANSI C》一书中读到,“>>”和“0; nwords--) sum += *buf++; sum = (sum >>
当下拉列表的选择发生变化时,我想: 1) 通过 div 在整个网站上显示一些 GUI 阻止覆盖 2)然后处理一些代码 3) 然后隐藏叠加层。 问题是,当我在事件监听器函数中编写此逻辑时,将执行 onC
我正在使用 Clojure 和 RESTEasy 设计 JAX-RS REST 服务器. 据我了解,用 Lisp 系列语言编写的应用程序比用“传统”命令式语言编写的应用程序更多地构建为“特定于领域的语
我目前正在研究一种替代出勤监控系统作为一项举措。目前,我设计的用户表单如下所示: Time Stamp Userform 它的工作原理如下: 员工将选择他/她将使用的时间戳类型:开始时间、超时、第一次
我是一名学生,试图自学编程,从在线资源和像您这样的人那里获得帮助。我在网上找到了一个练习来创建一个小程序来执行此操作: 编写一个程序,读取数字 a 和 b(长整型)并列出 a 和 b 之间有多少个数字
我正在尝试编写一个 shell 程序,给定一个参数,打印程序的名称和参数中的每个奇数词(即,不是偶数词)。但是,我没有得到预期的结果。在跟踪我的程序时,我注意到,尽管奇数词(例如,第 5 个词,5 %
只是想知道是否有任何 Java API 可以让您控制台式机/笔记本电脑外壳上的 LED? 或者,如果不可能,是否有可能? 最佳答案 如果你说的是前面的 LED 指示电源状态和 HDD 繁忙状态,恐怕没
我是一名优秀的程序员,十分优秀!