线程面试必备基础知识

当创建子线程时,怎么解决子线程是得不到父线程的 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 的作用

  1. 组合了 Callable,实现了 Runnable,把 Callable 和 Runnnable 串联了起来。
  2. 统一了有参任务和无参任务两种定义方式,方便了使用。
  3. 实现了 Future 的所有方法,对任务有一定的管理功能,比如说拿到任务执行结果,取消任务,打断任务等等。

FutureTask 的 get、cancel 方法

  1. FutureTask 的 get可以得到 Callable 异步任务执行的结果, 有无限阻塞和带超时时间两种方法。分别是get() 和get(long timeout, TimeUnit unit)。无参 get 会一直等待任务执行完成之后才返回,有参 get 方法可以设定固定的时间,在设定的时间内,如果任务还没有执行成功,直接返回异常。
  2. 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。因此陷入死锁

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值