大家好,又见面了,我是你们的朋友全栈君。
Java并发编程问题是面试过程中很容易遇到的问题,提前准备是解决问题的最好办法,将试题总结起来,时常查看会有奇效。
现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。
核心:
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。 比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。 想要更深入了解,建议看一下join的源码,也很简单的,使用wait方法实现的。
t.join(); //调用join方法,等待线程t执行完毕 t.join(1000); //等待 t 线程,等待时间是1000毫秒。
代码实现:
public static void main (String[] args) {
method01();
method02();
}
/** * 第一种实现方式,顺序写死在线程代码的内部了,有时候不方便 */
private static void method01 () {
Thread t1 = new Thread(new Runnable() {
@Override public void run () {
System.out.println("t1 is finished" );
}
});
Thread t2 = new Thread(new Runnable() {
@Override public void run () {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 is finished" );
}
});
Thread t3 = new Thread(new Runnable() {
@Override public void run () {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3 is finished" );
}
});
t3.start();
t2.start();
t1.start();
}
/** * 第二种实现方式,线程执行顺序可以在方法中调换 */
private static void method02 (){
Runnable runnable = new Runnable() {
@Override public void run () {
System.out.println(Thread.currentThread().getName() + "执行完成" );
}
};
Thread t1 = new Thread(runnable, "t1" );
Thread t2 = new Thread(runnable, "t2" );
Thread t3 = new Thread(runnable, "t3" );
try {
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
这个题的原答案我认为不是很全面。 Lock接口 和 ReadWriteLock接口 如下:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
整体上来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操作。另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。
ReadWriteLock是对Lock的运用,具体的实现类是 ReentrantReadWriteLock ,下面用这个类来实现读写类型的高效缓存:
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/** * 用ReadWriteLock读写锁来实现一个高效的Map缓存 * Created by LEO on 2017/10/30. */
public class ReaderAndWriter <K , V > {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private final Map<K, V> map;
public ReaderAndWriter(Map<K, V> map) {
this .map = map;
}
/************* 这是用lock()方法写的 ********************/
/************* 这是用tryLock()方法写的 ********************/
public V put(K key, V value){
while (true ){
if (writeLock.tryLock()){
try {
System.out.println("put " + key +" = " + value);
return map.put(key, value);
}finally {
writeLock.unlock();
}
}
}
}
public V get(K key){
while (true ){
if (readLock.tryLock()) {
try {
V v = map.get(key);
System.out.println("get " + key +" = " + v);
return v;
} finally {
readLock.unlock();
}
}
}
}
/******************** 下面是测试区 *********************************/
public static void main(String[] args) {
final ReaderAndWriter<String, Integer> rw = new ReaderAndWriter<>(new HashMap<>());
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0 ; i < 100 ; i++) {
exec.execute(new TestRunnable(rw));
}
exec.shutdown();
}
static class TestRunnable implements Runnable {
private final ReaderAndWriter<String, Integer> rw;
private final String KEY = "x" ;
TestRunnable(ReaderAndWriter<String, Integer> rw) {
this .rw = rw;
}
@Override
public void run() {
Random random = new Random();
int r = random.nextInt(100 );
if (r < 30 ){
rw.put(KEY, r);
} else {
rw.get(KEY);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
在java中wait和sleep方法的不同?
通常会在电话面试中经常被问到的Java线程面试问题。 最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
此处我想理一下Java多线程的基础知识: – Java的多线程锁是挂在对象上的,并不是在方法上的。即每个对象都有一个锁,当遇到类似synchronized的同步需要时,就会监视(monitor)每个想使用本对象的线程按照一定的规则来访问,规则也就是在同一时间内只能有一个线程能访问此对象。 – Java中获取锁的单位是线程。当线程A获取了对象B的锁,也就是对象B的持有标记上写的是线程A的唯一标识,在需要同步的情况下的话,只有线程A能访问对象B。 – Thread常用方法有:start/stop/yield/sleep/interrupt/join等,他们是线程级别的方法,所以并不会太关心锁的具体逻辑。 – Object的线程有关方法是:wait/wait(事件参数)/notify/notifyAll,他们是对象的方法,所以使用的时候就有点憋屈了,必须当前线程获取了本对象的锁才能使用,否则会报异常。但他们能更细粒度的控制锁,可以释放锁。
用Java实现阻塞队列。
这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。
下面是实现了阻塞的take和put方法的阻塞队列(分别用synchronized 和 wait/notify 实现):
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyBlocingQueue<E> {
private final List list;
private final int limit;
public MyBlocingQueue (int limit) {
this .limit = limit;
this .list = new LinkedList<E>();
}
public synchronized void put (E e){
while (list.size() == limit){
try {
wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
System.out .println("list : " + list.toString());
System.out .println("put : " + e);
list.add(e);
notifyAll();
}
public synchronized E take () {
while (list.size() == 0 ){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out .println("list : " + list.toString());
E remove = (E) list.remove(0 );
System.out .println("take : " + remove);
notifyAll();
return remove;
}
public static void main (String[] args) {
final MyBlocingQueue<Integer> myBlocingQueue = new MyBlocingQueue(10 );
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0 ; i < 100 ; i++) {
exec.execute(new TestRunnable(myBlocingQueue));
}
exec.shutdown();
}
static class TestRunnable implements Runnable{
private final MyBlocingQueue<Integer> myBlocingQueue;
TestRunnable(MyBlocingQueue<Integer> myBlocingQueue) {
this .myBlocingQueue = myBlocingQueue;
}
@Override
public void run () {
Random random = new Random();
int r = random.nextInt(100 );
if (r < 30 ){
myBlocingQueue.put(r);
} else {
myBlocingQueue.take();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
BlockingQueue介绍:
Java5中提供了BlockingQueue的方法,并且有几个实现,在此介绍一下。
BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:
Throws exception
Special value
Blocks
Times out
add(e)
offer(e)
put(e)
offer(Object, long, TimeUnit)
remove()
poll()
take()
poll(long, TimeUnit)
element()
peek()
– Throws exception 抛异常:如果试图的操作无法立即执行,抛一个异常。 – Special value 特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false) – Blocks 阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。 – Times out 超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是true / false)。
BlockingQueue 的实现类 – ArrayBlockingQueue :ArrayBlockingQueue 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。有界也就意味着,它不能够存储无限多数量的元素。它有一个同一时间能够存储元素数量的上限。你可以在对其初始化的时候设定这个上限,但之后就无法对这个上限进行修改了(译者注:因为它是基于数组实现的,也就具有数组的特性:一旦初始化,大小就无法修改)。 – DelayQueue :DelayQueue 对元素进行持有直到一个特定的延迟到期。注入其中的元素必须实现 java.util.concurrent.Delayed 接口。 – LinkedBlockingQueue :LinkedBlockingQueue 内部以一个链式结构(链接节点)对其元素进行存储。如果需要的话,这一链式结构可以选择一个上限。如果没有定义上限,将使用 Integer.MAX_VALUE 作为上限。 – PriorityBlockingQueue :PriorityBlockingQueue 是一个无界的并发队列。它使用了和类 java.util.PriorityQueue 一样的排序规则。你无法向这个队列中插入 null 值。所有插入到 PriorityBlockingQueue 的元素必须实现 java.lang.Comparable 接口。因此该队列中元素的排序就取决于你自己的 Comparable 实现。 – SynchronousQueue :SynchronousQueue 是一个特殊的队列,它的内部同时只能够容纳单个元素。如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走。同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素。据此,把这个类称作一个队列显然是夸大其词了。它更多像是一个汇合点。
BlocingQueue的实现大多是通过 lock锁的多条件(condition)阻塞控制,下面我们自己写一个简单版:
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyBlocingQueue2<E> {
private final List list;
private final int limit;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock .newCondition();
private final Condition notEmpty = lock .newCondition();
public MyBlocingQueue2 (int limit) {
this .limit = limit;
this .list = new LinkedList<E>();
}
public void put (E e) throws InterruptedException {
lock .lock ();
try {
while (list.size() == limit){
notFull.await ();
}
list.add(e);
notEmpty.signalAll();
}finally {
lock .unlock();
}
}
public E take () throws InterruptedException {
lock .lock ();
try {
while (list.size() == 0 ){
notEmpty.await ();
}
E remove = (E) list.remove(0 );
notFull.signalAll();
return remove;
}finally {
lock .unlock();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
用Java写代码来解决生产者——消费者问题。
与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。
生产者、消费者有很多的实现方法: – 用wait() / notify()方法 – 用Lock的多Condition方法 – BlockingQueue阻塞队列方法
可以发现在上面实现阻塞队列题中,BlockingQueue的实现基本都用到了类似的实现,将BlockingQueue的实现方式稍微包装一下就成了一个生产者-消费者模式了。
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ProduceAndConsumer {
public static void main(String[] args) {
final BlockingQueue<Integer> list = new ArrayBlockingQueue<Integer>(10 );
Procude procude = new Procude(list );
Consumer consumer = new Consumer(list );
procude.start();
consumer.start();
}
static class Procude extends Thread {
private final BlockingQueue<Integer> list ;
Procude(BlockingQueue<Integer> list ) {
this.list = list ;
}
@Override public void run() {
while (true ){
try {
Integer take = list .take();
System.out.println("消费数据:" + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Consumer extends Thread {
private final BlockingQueue<Integer> list ;
Consumer(BlockingQueue<Integer> list ) {
this.list = list ;
}
@Override public void run() {
while (true ){
try {
int i = new Random().nextInt(100 );
list .put(i);
System.out.println("生产数据:" + i);
Thread.sleep(1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
此处不再详细地写另外几种实现方式了:wait() / notify()方法、Lock的多Condition方法、信号量等,甚至可以考虑用CyclicBarrier、CountDownLatch也可以实现生产者-消费者的,难易程度、效率不一样罢了。
用Java写一个会导致死锁的程序,你将怎么解决?
这是我最喜欢的Java线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免Java中的死锁来得到关于死锁的更多信息。
/** * 简单死锁程序 * lockA、lockB分别是两个资源,线程A、B必须同是拿到才能工作 * 但A线程先拿lockA、再拿lockB * 线程先拿lockB、再拿lockA * @author xuexiaolei * @version 2017年11月01日 */
public class SimpleDeadLock {
public static void main(String[] args) {
Object lockA = new Object();
Object lockB = new Object();
A a = new A(lockA, lockB);
B b = new B(lockA, lockB);
a.start();
b.start();
}
static class A extends Thread {
private final Object lockA;
private final Object lockB;
A(Object lockA, Object lockB) {
this .lockA = lockA;
this .lockB = lockB;
}
@Override public void run() {
synchronized (lockA){
try {
Thread.sleep(1000 );
synchronized (lockB){
System.out.println("Hello A" );
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class B extends Thread {
private final Object lockA;
private final Object lockB;
B(Object lockA, Object lockB) {
this .lockA = lockA;
this .lockB = lockB;
}
@Override public void run() {
synchronized (lockB){
try {
Thread.sleep(1000 );
synchronized (lockA){
System.out.println("Hello B" );
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
产生死锁的四个必要条件: – 互斥条件:一个资源每次只能被一个进程使用。 – 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 – 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 – 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免死锁? – 从死锁的四个必要条件来看,破坏其中的任意一个条件就可以避免死锁。但互斥条件是由资源本身决定的,不剥夺条件一般无法破坏,要实现的话得自己写更多的逻辑。 – 避免无限期的等待:用Lock.tryLock(),wait/notify等方法写出请求一定时间后,放弃已经拥有的锁的程序。 – 注意锁的顺序:以固定的顺序获取锁,可以避免死锁。 – 开放调用:即只对有请求的进行封锁。你应当只想你要运行的资源获取封锁,比如在上述程序中我在封锁的完全的对象资源。但是如果我们只对它所属领域中的一个感兴趣,那我们应当封锁住那个特殊的领域而并非完全的对象。 – 最后,如果能避免使用多个锁,甚至写出无锁的线程安全程序是再好不过了。
什么是原子操作,Java中的原子操作是什么?
非常简单的java线程面试问题,接下来的问题是你是否需要同步一个原子操作。
原子操作是不可分割的操作,一个原子操作中间是不会被其他线程打断的,所以不需要同步一个原子操作。 多个原子操作合并起来后就不是一个原子操作了,就需要同步了。 i++不是一个原子操作,它包含 读取-修改-写入 操作,在多线程状态下是不安全的。 另外,java内存模型允许将64位的读操作或写操作分解为2个32位的操作,所以对long和double类型的单次读写操作并不是原子的,注意使用volitile使他们成为原子操作。
Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?
自从Java 5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性。
volatile关键字的作用是:保证变量的可见性。 在java内存结构中,每个线程都是有自己独立的内存空间(此处指的线程栈)。当需要对一个共享变量操作时,线程会将这个数据从主存空间复制到自己的独立空间内进行操作,然后在某个时刻将修改后的值刷新到主存空间。这个中间时间就会发生许多奇奇怪怪的线程安全问题了,volatile就出来了,它保证读取数据时只从主存空间读取,修改数据直接修改到主存空间中去,这样就保证了这个变量对多个操作线程的可见性了。换句话说,被volatile修饰的变量,能保证该变量的 单次读或者单次写 操作是原子的。
但是线程安全是两方面需要的 原子性(指的是多条操作)和可见性。volatile只能保证可见性,synchronized是两个均保证的。 volatile轻量级,只能修饰变量;synchronized重量级,还可修饰方法。 volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞。
什么是竞争条件(race condition)?你怎样发现和解决的?
这是一道出现在多线程面试的高级阶段的问题。大多数的面试官会问最近你遇到的竞争条件,以及你是怎么解决的。有些时间他们会写简单的代码,然后让你检测出代码的竞争条件。可以参考我之前发布的关于Java竞争条件的文章。在我看来这是最好的java线程面试问题之一,它可以确切的检测候选者解决竞争条件的经验。关于这方面最好的书是《java并发编程实战》。
竞争条件,在《java并发编程实战》叫做竞态条件:指设备或系统出现不恰当的执行时序,而得到不正确的结果。
下面是个最简单的例子,是一个单例模式实现的错误示范:
@NotThreadSafe
public class LazyInitRace {
private ExpensiveObject instance = null ;
public ExpensiveObject getInstance () {
if (instance == null )
instance = new ExpensiveObject();
return instance;
}
}
在上述例子中,表现一种很常见的竞态条件类型:“先检查后执行”。根据某个检查结果来执行进一步的操作,但很有可能这个检查结果是失效的!还有很常见的竞态条件“读取-修改-写入”三连,在多线程条件下,三个小操作并不一定会放在一起执行的。
如何对待竞态条件? 首先,警惕复合操作,当多个原子操作合在一起的时候,并不一定仍然是一个原子操作,此时需要用同步的手段来保证原子性。 另外,使用本身是线程安全的类,这样在很大程度上避免了未知的风险。
你将如何使用thread dump?你将如何分析Thread dump?
在UNIX中你可以使用kill -3,然后thread dump将会打印日志,在windows中你可以使用”CTRL+Break”。非常简单和专业的线程面试问题,但是如果他问你怎样分析它,就会很棘手。
SIGQUIT(kill -3 pid)用来打印Java进程trace,并不会影响程序运行,不用担心他把程序杀死了;SIGUSR1(kill -10 pid)可触发进程进行一次强制GC。
java线程的状态转换介绍
后续分析要用到,所以此处穿插一下这个点:
新建状态(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/O请求时,就会进入这个状态。
死亡状态(Dead) 当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。
我们运行之前的那个死锁代码SimpleDeadLock.java,然后尝试输出信息(/*这是注释,作者自己加的*/):
2017 -11 -01 17 :36 :28
Full thread dump Java HotSpot(TM) 64 -Bit Server VM (25.144 -b01 mixed mode):
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000001c88800 nid=0x1c18 waiting on condition [0x0000000000000000]
java.lang .Thread .State : RUNNABLE
"Thread-1" #12 prio=5 os_prio=0 tid=0x0000000018d49000 nid=0x17b8 waiting for monitor entry [0x0000000019d7f000]
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=0x1ebc waiting for monitor entry [0x000000001907f000]
java.lang .Thread .State : BLOCKED (on object monitor)
at 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 on condition [0x0000000000000000]
java.lang .Thread .State : RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000000018be4800 nid=0x1db4 waiting on condition [0x0000000000000000]
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 [0x00000000193ce000]
java.lang .Thread .State : RUNNABLE
at java.net .SocketInputStream .socketRead 0(Native Method)
at java.net .SocketInputStream .socketRead (SocketInputStream.java :116 )
at java.net .SocketInputStream .read (SocketInputStream.java :171 )
at java.net .SocketInputStream .read (SocketInputStream.java :141 )
at sun.nio .cs .StreamDecoder .readBytes (StreamDecoder.java :284 )
at sun.nio .cs .StreamDecoder .implRead (StreamDecoder.java :326 )
at sun.nio .cs .StreamDecoder .read (StreamDecoder.java :178 )
- locked <0x00000000d632b928 > (a java.io .InputStreamReader )
at java.io .InputStreamReader .read (InputStreamReader.java :184 )
at java.io .BufferedReader .fill (BufferedReader.java :161 )
at java.io .BufferedReader .readLine (BufferedReader.java :324 )
- locked <0x00000000d632b928 > (a java.io .InputStreamReader )
at java.io .BufferedReader .readLine (BufferedReader.java :389 )
at com .intellij .rt .execution .application .AppMainV 2$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=0xdac in Object.wait() [0x0000000018b6f000]
java.lang .Thread .State : WAITING (on object monitor)
at java.lang .Object .wait (Native Method)
- waiting on <0x00000000d6108ec8 > (a java.lang .ref .ReferenceQueue $Lock)
at java.lang .ref .ReferenceQueue .remove (ReferenceQueue.java :143 )
- locked <0x00000000d6108ec8 > (a java.lang .ref .ReferenceQueue $Lock)
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=2 tid=0x0000000017723800 nid=0x1670 in Object.wait() [0x00000000189ef000]
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 .Reference $ReferenceHandler.run (Reference.java :153 )
"VM Thread" os_prio=2 tid=0x000000001771b800 nid=0x604 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000001c9d800 nid=0x9f0 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000001c9f000 nid=0x154c runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000001ca0800 nid=0xcd0 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000001ca2000 nid=0x1e58 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x0000000018c5a000 nid=0x1b58 waiting on condition
JNI global references: 33
Found one Java-level deadlock:
=============================
"Thread-1" :
waiting to lock monitor 0x0000000017729fc8 (object 0x00000000d629b4d8 , a java.lang .Object ),
which is held by "Thread-0"
"Thread-0" :
waiting to lock monitor 0x0000000017727738 (object 0x00000000d629b4e8 , 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.
Heap
PSYoungGen total 37888 K, used 4590 K [0x00000000d6100000 , 0x00000000d8b00000 , 0x0000000100000000 )
eden space 32768 K, 14 % used [0x00000000d6100000 ,0x00000000d657b968 ,0x00000000d8100000 )
from space 5120 K, 0 % used [0x00000000d8600000 ,0x00000000d8600000 ,0x00000000d8b00000 )
to space 5120 K, 0 % used [0x00000000d8100000 ,0x00000000d8100000 ,0x00000000d8600000 )
ParOldGen total 86016 K, used 0 K [0x0000000082200000 , 0x0000000087600000 , 0x00000000d6100000 )
object space 86016 K, 0 % used [0x0000000082200000 ,0x0000000082200000 ,0x0000000087600000 )
Metaspace used 3474 K, capacity 4500 K, committed 4864 K, reserved 1056768 K
class space used 382 K, capacity 388 K, committed 512 K, reserved 1048576 K
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
这是另一个非常经典的java多线程面试问题。这也是我刚开始写线程程序时候的困惑。现在这个问题通常在电话面试或者是在初中级Java面试的第一轮被问到。这个问题的回答应该是这样的,当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。
简单点来说: new一个Thread,线程进入了新建状态;调用start()方法,线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start()会执行线程的相应准备工作,然后自动执行run()方法的内容。是真正的多线程工作。 而直接执行run()方法,会把run方法当成一个mian线程下的普通方法去执行,并不会在某个线程中执行它,这并不是多线程工作。
Java中你怎样唤醒一个阻塞的线程?
这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了IO阻塞,我并且不认为有一种方法可以中止线程。如果线程因为调用wait()、sleep()、或者join()方法而导致的阻塞,你可以中断线程,并且通过抛出InterruptedException来唤醒它。我之前写的《How to deal with blocking methods in java》有很多关于处理线程阻塞的信息。
这个我们先简单粗暴地对某些阻塞方法进行分类: – 会抛出InterruptedException的方法:wait、sleep、join、Lock.lockInterruptibly等,针对这类方法,我们在线程内部处理好异常(要不完全内部处理,要不把这个异常抛出去),然后就可以实现唤醒。 – 不会抛InterruptedException的方法:Socket的I/O,同步I/O,Lock.lock等。对于I/O类型,我们可以关闭他们底层的通道,比如Socket的I/O,关闭底层套接字,然后抛出异常处理就好了;比如同步I/O,关闭底层Channel然后处理异常。对于Lock.lock方法,我们可以改造成Lock.lockInterruptibly方法去实现。
在Java中CycliBarriar和CountdownLatch有什么区别?
这个线程问题主要用来检测你是否熟悉JDK5中的并发包。这两个的区别是CyclicBarrier可以重复使用已经通过的障碍,而CountdownLatch不能重复使用。
还要注意一点的区别: CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。 CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。 这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”, 是它在等待,而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。 从api上理解就是CountdownLatch有主要配合使用两个方法countDown()和await(),countDown()是做事的线程用的方法,await()是等待事情完成的线程用个方法,这两种线程是可以分开的(下面例子:CountdownLatchTest2),当然也可以是同一组线程(下面例子:CountdownLatchTest);CyclicBarrier只有一个方法await(),指的是做事线程必须大家同时等待,必须是同一组线程的工作。
CountdownLatch例子:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** * 线程都准备完成后一起执行的例子 * @author xuexiaolei * @version 2017年11月02日 */
public class CountdownLatchTest {
private final static int THREAD_NUM = 10 ;
public static void main (String[] args) {
CountDownLatch lock = new CountDownLatch(THREAD_NUM);
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0 ; i < THREAD_NUM; i++) {
exec.submit(new CountdownLatchTask(lock, "Thread-" +i));
}
exec.shutdown();
}
static class CountdownLatchTask implements Runnable{
private final CountDownLatch lock;
private final String threadName;
CountdownLatchTask(CountDownLatch lock, String threadName) {
this .lock = lock;
this .threadName = threadName;
}
@Override public void run () {
for (int i = 0 ; i < 3 ; i++) {
System.out.println(threadName + " 准备完成" );
lock.countDown();
try {
lock.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " 执行完成" );
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** * 各个线程执行完成后,主线程做总结性工作的例子 * @author xuexiaolei * @version 2017年11月02日 */
public class CountdownLatchTest2 {
private final static int THREAD_NUM = 10 ;
public static void main (String[] args) {
CountDownLatch lock = new CountDownLatch(THREAD_NUM);
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0 ; i < THREAD_NUM; i++) {
exec.submit(new CountdownLatchTask(lock, "Thread-" +i));
}
try {
lock.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("大家都执行完成了,做总结性工作" );
exec.shutdown();
}
static class CountdownLatchTask implements Runnable{
private final CountDownLatch lock;
private final String threadName;
CountdownLatchTask(CountDownLatch lock, String threadName) {
this .lock = lock;
this .threadName = threadName;
}
@Override public void run () {
System.out.println(threadName + " 执行完成" );
lock.countDown();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
CyclicBarrier例子:
import java.util.concurrent.*;
/** * * @author xuexiaolei * @version 2017年11月02日 */
public class CyclicBarrierTest {
private final static int THREAD_NUM = 10 ;
public static void main (String[] args) {
CyclicBarrier lock = new CyclicBarrier(THREAD_NUM, new Runnable() {
@Override public void run () {
System.out.println("大家都准备完成了" );
}
});
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0 ; i < THREAD_NUM; i++) {
exec.submit(new CountdownLatchTask(lock, "Thread-" +i));
}
exec.shutdown();
}
static class CountdownLatchTask implements Runnable{
private final CyclicBarrier lock;
private final String threadName;
CountdownLatchTask(CyclicBarrier lock, String threadName) {
this .lock = lock;
this .threadName = threadName;
}
@Override public void run () {
for (int i = 0 ; i < 3 ; i++) {
System.out.println(threadName + " 准备完成" );
try {
lock.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " 执行完成" );
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
什么是不可变对象,它对写并发应用有什么帮助?
另一个多线程经典面试问题,并不直接跟线程有关,但间接帮助很多。这个java面试问题可以变的非常棘手,如果他要求你写一个不可变对象,或者问你为什么String是不可变的。
immutable Objects(不可变对象)就是那些一旦被创建,它们的状态就不能被改变的Objects,每次对他们的改变都是产生了新的immutable的对象,而mutable Objects(可变对象)就是那些创建后,状态可以被改变的Objects.
如何在Java中写出Immutable的类? 1. immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象。 2. immutable类的所有的属性都应该是final的。 3. 对象必须被正确的创建,比如:对象引用在对象创建过程中不能泄露(leak)。 4. 对象应该是final的,以此来限制子类继承父类,以避免子类改变了父类的immutable特性。 5. 如果类中包含mutable类对象,那么返回给客户端的时候,返回该对象的一个拷贝,而不是该对象本身(该条可以归为第一条中的一个特例)
使用Immutable类的好处: 1. Immutable对象是线程安全的,可以不用被synchronize就在并发环境中共享 2.Immutable对象简化了程序开发,因为它无需使用额外的锁机制就可以在线程间共享 3. Immutable对象提高了程序的性能,因为它减少了synchroinzed的使用 4. Immutable对象是可以被重复使用的,你可以将它们缓存起来重复使用,就像字符串字面量和整型数字一样。你可以使用静态工厂方法来提供类似于valueOf()这样的方法,它可以从缓存中返回一个已经存在的Immutable对象,而不是重新创建一个。
/** * 不可变对象 * @author xuexiaolei * @version 2017年11月03日 */
public class ImmutableObjectPerson {
private final String name;
private final String sex;
public ImmutableObjectPerson (String name, String sex) {
this .name = name;
this .sex = sex;
}
public String getName () {
return name;
}
public String getSex () {
return sex;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的?
多线程和并发程序中常遇到的有Memory-interface、竞争条件、死锁、活锁和饥饿。问题是没有止境的,如果你弄错了,将很难发现和调试。这是大多数基于面试的,而不是基于实际应用的Java线程问题。
此类问题请大家面试的时候提前准备,方便交流,如果实在找不出来,可以想想自己平时解决问题的思路,总结下来告诉考官。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/159854.html 原文链接:https://javaforall.cn
【正版授权,激活自己账号】:
Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】:
官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...