当创建子线程时,怎么解决子线程是得不到父线程的 ThreadLocal,
使用 InheritableThreadLocal 来代替 ThreadLocal,ThreadLocal 和 InheritableThreadLocal 都是线程的属性,所以可以做到线程之间的数据隔离,在多线程环境下我们经常使用,但在有子线程被创建的情况下,父线程 ThreadLocal 是无法传递给子线程的,但 InheritableThreadLocal 可以,主要是因为在线程创建的过程中,会把InheritableThreadLocal 里面的所有值传递给子线程。
具体源码
// 当父线程的 inheritableThreadLocals 的值不为空时
// 会把 inheritableThreadLocals 里面的值全部传递给子线程
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
线程创建的方式
主要分成三种,分成两大类,分别是有返回值和无返回值。
无返回值
//第一种是继承 Thread
public class Main {
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is run");
}
}
public static void main(String[] args) {
new Main().new MyThread().start();
}
}
/**输出
Thread-0 is run
/
//第二种是实现 Runnable 接口,并作为 Thread 构造器的入参
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is run");
}
});
thread.start();
}
}
/**输出
Thread-0 is run
/
这两种都会开一个子线程去执行任务,并且是没有返回值的
有返回值
//把 FutureTask 作为 Thread 的入参,并在FutureTask里实现Callable接口,使其对任务有一定的管理功能。
public class Main {
public static void main(String[] args) {
FutureTask futureTask = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName() + " is run");
String result = "Hello world";
return result;
}
});
new Thread(futureTask).start();
try {
String result = (String) futureTask.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**输出
Thread-0 is run
Hello world
/
实现子线程的让步执行
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是thread1,开始沉睡");
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是thread1,执行结束");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是thread2,开始运行");
try{
System.out.println("我是thread2,我在等待thread1");
thread1.join();
System.out.println("我是thread2,thread1执行结束,thread2继续执行");
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是thread2,执行结束");
}
});
thread1.start();
thread2.start();
}
}
/**输出
我是thread1,开始沉睡
我是thread2,开始运行
我是thread2,我在等待thread1
我是thread1,执行结束
我是thread2,thread1执行结束,thread2继续执行
我是thread2,执行结束
/
子线程 2 需要等待子线程 1,只需要子线程 2 运行的时候,调用子线程 1的 join 方法即可,这样线程 2执行到 join 代码时,就会等待线程 1 执行完成之后,才会继续执行。
守护线程和非守护线程的区别
两者的主要区别是,在 JVM 退出时,JVM 是不会管守护线程的,只会管非守护线程,如果非守护线程还有在运行的,JVM 就不会退出,如果没有非守护线程了,但还有守护线程的,JVM 直接退出
在项目启动的时候收集代码信息,选择守护线程还是非守护线程
如果需要在项目启动的时候收集代码信息,就需要看收集工作是否重要了,如果不太重要,又很耗时,就应该选择守护线程,这样不会妨碍 JVM 的退出,如果收集工作非常重要的话,那么就需要非守护进程,这样即使启动时发生未知异常,JVM 也会等到代码收集信息线程结束后才会退出,不会影响收集工作。
线程 start 和 run 之间的区别。
调用 Thread.start 方法会开一个新的子线程,而run不会开一个新的子线程,而且用当前主线程来执行
Thread、Runnable、Callable 三者之间的区别
-
Thread 实现了 Runnable,本身就是 Runnable,但同时负责线程创建、线程状态变更等操作
-
Runnable 是无返回值任务接口
-
Callable是有返回值任务接口
-
Runnable 和 Callable 只是任务的定义,具体执行还需要靠 Thread。
如何适配线程池 submit 的两个方法。
线程池 submit方法一个可接受 Runnable,一个可接受 Callable,但两个方法底层的逻辑却是同一套。Runnable 和 Callable 是通过 FutureTask 进行统一的,FutureTask 有个属性是 Callable,同时也实现了 Runnable 接口,两者的统一转化是在 FutureTask 的构造器里实现的,FutureTask 的最终目标是把 Runnable 和 Callable 都转化成 Callable,Runnable 转化成 Callable 是通过 RunnableAdapter 适配器进行实现的。
Callable 能否丢给 Thread 去执行
可以的,可以新建 Callable,并作为 FutureTask 的构造器入参,然后把 FutureTask 丢给 Thread 去执行即可
FutureTask 的作用
- 组合了 Callable,实现了 Runnable,把 Callable 和 Runnnable 串联了起来。
- 统一了有参任务和无参任务两种定义方式,方便了使用。
- 实现了 Future 的所有方法,对任务有一定的管理功能,比如说拿到任务执行结果,取消任务,打断任务等等。
FutureTask 的 get、cancel 方法
- FutureTask 的 get可以得到 Callable 异步任务执行的结果, 有无限阻塞和带超时时间两种方法。分别是get() 和get(long timeout, TimeUnit unit)。无参 get 会一直等待任务执行完成之后才返回,有参 get 方法可以设定固定的时间,在设定的时间内,如果任务还没有执行成功,直接返回异常。
- cancel 是取消任务,如果任务还没有执行,是可以取消的,如果任务已经在执行过程中了,你可以选择不取消,或者直接打断执行中的任务
wait()和sleep()的相同点和区别
相同点
- 两者都让线程进入到 TIMED_WAITING 状态,并且可以设置等待的时间。
不同点
- sleep 不会释放锁,沉睡的时候,其它线程是无法获得锁的。而 wait 会释放锁
- sleep 是 Thread 类的方法,wait 是 Object 类的方。
Thread.yield 方法的作用
yield 方法表示当前线程放弃 cpu,重新参与到 cpu 的竞争中去,再次竞争时,自己有可能得到 cpu 资源,也有可能得不到,这样做的目的是防止当前线程一直霸占 cpu。
死锁Demo
public class Test {
// 共享变量 1
private static final Object share1 = new Object();
// 共享变量 2
private static final Object share2 = new Object();
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (share1) {
System.out.println("thread1 get share1");
System.out.println("thread1 ready to get share2");
synchronized (share2) {
System.out.println("thread1 get share2");
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (share2) {
System.out.println("thread2 get share2");
System.out.println("thread2 ready to get share1");
synchronized (share1) {
System.out.println("thread2 get share1");
}
}
}
});
thread1.start();
thread2.start();
}
}
当线程1想去获取share2的时候,线程2已经得到了share2,锁住share2了,线程1就会一直等待share2。
当线程2想去获取share1的时候,线程1已经得到了share1,锁住share1了,线程2就会一直等待share1。因此陷入死锁