【Java并发编程】Java多线程(三):关于线程的几个问题
1.子线程 1 去等待子线程 2 执行完成之后才能执行,如何去实现?
答:这里考察的就是 Thread.join 方法,我们可以这么做:
@Test
public void testJoin2() throws Exception {
// 线程2
Thread thread2 = new Thread(new Runnable() {
public void run() {...}
});
// 线程1
Thread thread1 = new Thread(new Runnable() {
public void run() {
// join使调用线程,在当前线程之前结束
thread2.join();
}
});
thread1.start();
thread2.start();
Thread.sleep(100000);
}
子线程 1 需要等待子线程 2,只需要子线程 1 运行的时候,调用子线程 2 的 join 方法即可,这样线程 1 执行到 join 代码时,就会等待线程 2 执行完成之后,才会继续执行。
2.守护线程和非守护线程的区别?如果我想在项目启动的时候收集代码信息,请问是守护线程好,还是非守护线程好,为什么?
答:两者的主要区别是,在 JVM 退出时,JVM 是不会管守护线程的,只会管非守护线程,如果非守护线程还有在运行的,JVM 就不会退出,如果没有非守护线程了,但还有守护线程的,JVM 直接退出。
如果需要在项目启动的时候收集代码信息,就需要看收集工作是否重要了,如果不太重要,又很耗时,就应该选择守护线程,这样不会妨碍 JVM 的退出,如果收集工作非常重要的话,那么就需要非守护进程,这样即使启动时发生未知异常,JVM 也会等到代码收集信息线程结束后才会退出,不会影响收集工作。
3.线程 start 和 run 之间的区别?
答:调用 Thread.start 方法会开一个新的线程,run 方法不会。
4.线程池 submit 有两个方法,方法一可接受 Runnable,方法二可接受 Callable,但两个方法底层的逻辑却是同一套,这是如何适配的?
答:问题考察点在于 Runnable 和 Callable 之间是如何转化的,可以这么回答。
Runnable 和 Callable 是通过 FutureTask 进行统一的,FutureTask 有个属性是 Callable,同时也实现了 Runnable 接口,两者的统一转化是在 FutureTask 的构造器里实现的,FutureTask 的最终目标是把 Runnable 和 Callable 都转化成 Callable,Runnable 转化成 Callable 是通过 RunnableAdapter 适配器进行实现的。
线程池的 submit 底层的逻辑只认 FutureTask,不认 Runnable 和 Callable 的差异,所以只要都转化成 FutureTask,底层实现都会是同一套。
5.Callable 能否丢给 Thread 去执行?
答:可以的,可以新建 Callable,并作为 FutureTask 的构造器入参,然后把 FutureTask 丢给 Thread 去执行即可。
6.Thread.yield 方法在工作中有什么用?
答:yield 方法表示当前线程放弃 cpu,重新参与到 cpu 的竞争中去,再次竞争时,自己有可能得到 cpu 资源,也有可能得不到,这样做的好处是防止当前线程一直霸占 cpu。
我们在工作中可能会写一些 while 自旋的代码,如果我们一直 while 自旋,不采取任何手段,我们会发现 cpu 一直被当前 while 循环占用,如果能预见 while 自旋时间很长,我们会设置一定的判断条件,让当前线程陷入阻塞,如果能预见 while 自旋时间很短,我们通常会使用 Thread.yield 方法,使当前自旋线程让步,不一直霸占 cpu,比如这样:
boolean stop = false;
while (!stop){
// dosomething
Thread.yield();
}
7.写一个简单的死锁 demo
// 共享变量 1
private static final Object share1 = new Object();
// 共享变量 2
private static final Object share2 = new Object();
@Test
public void testDeadLock() throws InterruptedException {
// 初始化线程 1,线程 1 需要在锁定 share1 共享资源的情况下再锁定 share2
Thread thread1 = new Thread(() -> {
synchronized (share1){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (share2){
log.info("{} is run",Thread.currentThread().getName());
}
}
});
// 初始化线程 2,线程 2 需要在锁定 share2 共享资源的情况下再锁定 share1
Thread thread2 = new Thread(() -> {
synchronized (share2){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (share1){
log.info("{} is run",Thread.currentThread().getName());
}
}
});
// 当线程 1、2 启动后,都在等待对方锁定的资源,但都得不到,造成死锁
thread1.start();
thread2.start();
Thread.sleep(1000000000);
}
8.创建子线程时,子线程是得不到父线程的 ThreadLocal,有什么办法可以解决这个问题?
答:可以使用 InheritableThreadLocal 来代替 ThreadLocal,ThreadLocal 和 InheritableThreadLocal 都是线程的属性,所以可以做到线程之间的数据隔离,在多线程环境下我们经常使用,但在有子线程被创建的情况下,父线程 ThreadLocal 是无法传递给子线程的,但 InheritableThreadLocal 可以,主要是因为在线程创建的过程中,会把InheritableThreadLocal 里面的所有值传递给子线程,具体代码如下:
// 当父线程的 inheritableThreadLocals 的值不为空时
// 会把 inheritableThreadLocals 里面的值全部传递给子线程
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
作者:A minor
来源:CSDN