面试官收题之1《JAVA多线程-线程基础概念》

518 阅读6分钟

欢迎分享,原创不易,请带上作者信息,谢谢

掘金栏目地址: juejin.cn/collection/…

今天和面试官朋友闲聊的时候,他说最近面了一位JAVA开发,他本着认真负责(炫技)的态度,问了一些JAVA多线程的问题竟然被面试者正面刚了一波!现在的面试者越来越厉害了,魔鬼的细节懂的越来越多,搞的他慌的一匹。

别慌,来我这里收集题目,明天继续和他正面刚!

我们当然是由浅入深,试试他的深浅,慢慢调情,不是,慢慢从入门开始。

线程、进程、程序?

什么是线程、什么是进程、什么是程序?

上来先给他醍醐灌顶,三连问让他瞬间懵逼,否则还以为大老虎是吃素的,嘿嘿嘿。

看着他微微泛红的脸颊,不知所措的眼神,我的目的就达到了,嘿嘿嘿。

沉默了十秒,我清了清嗓子。

年轻人,基础还不够扎实阿,你想想,程序不就是你写的代码?它不是静静的躺在你的硬盘上吗?进程不就是操作系统把你的代码跑起来了,然后再根据一些规则来调度这个进程吗?线程就更好理解了,你一个进程内部是不是可以启动多个线程干不同的事情?那么线程就是一段程序不同的执行路径。

总的来说

  • 1.程序:一段静态的代码
  • 2.进程:运行起来的程序
  • 3.线程:进程的最小执行单位,一段程序不同的执行路径

看着面试者崇拜的眼神,我知道,又征服一位小菜鸟。

并发与并行

我们这边呢,经常有一些高并发的场景,简单的说一说你怎么理解并发与并行的吧。

一个简单而又深刻的问题,关键是在不经意间突出了我们大流量高并发的架构和场景,我们可不是low X 的CURD,我简直是天才面试官。

  • 并发:同一时刻发出请求,可以同时执行,也可能不同时执行。主要指的是任务提交这个动作。
  • 并行:任务真正的同时执行,在多核CPU的情况,多个CPU同时工作。主要指的是任务执行的过程。

并发concurrent,比如我们可以对Redis进行并发请求,但是因为他的单线程模型,所以他不是并行执行(先别扯I/O Thread)。 并行的话parallel,JAVA的垃圾回收器中就有这种类型的存在。

JAVA中创建线程的方式

不错,可以说清楚还能举一反三,那你知道JAVA中创建线程的方式有几种?

面试者:这个我知道,两种,实现Runnable接口,继承Thread类。

嗯,不错,还能想出其他的答案吗?

额、、沉默了十秒,很明显他慌了,我又得逞了,底层基本就这两种,但是为了显示我高超的水平,怎么也要给他再整两个答案,嘿嘿嘿。

其实你说的不错,底层就基本这两种,你有没有了解过线程池也可以创建线程,Callable和Lambda表达式也可以创建线程?

还好他是小菜鸟,不然我要被怼死了,我真是一个机灵的小杠精。

  • 1.实现Runnable接口
  • 2.继承Thread类
  • 3.其他的可以不说,实在要刚,线程池,Callable和Lambda表达

JAVA线程的状态

那好吧,我们来点简单的(其实不是那么简单,嘿嘿),JAVA的线程状态有哪些?另外列举几个改变线程状态的方法。

最后一个小问肯定打他一个措手不及,估计他都没听明白什么意思。

面试者:JAVA线程的状态有,初始、就绪、运行、阻塞、结束,改变线程的状态是指sleep?wait?

嗯,你这个答案我觉得可以,不过答的还不够完美,阻塞态还可以进一步细分,我画一张图你就明白了。

锁与CPU资源

synchronized你平时应该经常用吧?上面给你列了这么多的方法,你知道哪些方法会释放锁和CPU资源,哪些不会吗

虽然这个问题和synchronized联系不是那么大,我直接给他定性他平常经常用synchronized,让他措手不及,毫无反驳之力,问题中深埋的陷阱让他知道社会的险恶。

  • sleep 不释放锁、释放cpu,时间到期回到Ready状态,等待cpu调度

  • join 释放锁、抢占cpu,子线程运行完,父线程才继续

  • yield 不释放锁、释放cpu,直接回到Ready状态,等待cpu调度

  • wait 释放锁、释放cpu

  • notify、notifyAll不释放锁、不释放cpu

  • Locksupport 和synchronize锁没什么关系

  • park 阻塞当前线程,释放CPU

  • unpark 解除阻塞不释放CPU

不错,回答比较全面,join真的会释放锁吗?你来看看我这个程序。

public class JoinReleaseTest {

    public static void main(String[] args) throws InterruptedException {
        final Object lock = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("t1能打印,说明释放锁了 ========");
            }
        });
        
        synchronized (lock) {
            t1.start();
            t1.join();
            System.out.println("main 执行完毕========");
        }
    }
}

//运行结果:无限等待

这是为啥?不是是放锁吗?为什么程序执行不下去了??

看着他慌乱的小眼神,我一定在他心里留下了不可磨灭的印象,哈哈哈哈。

沉默数十秒,是时候出手拯救他一下了。

注意这个问题我只讲一遍, 你看看,join的底层实现。

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

是不是用了一个循环,判断当前线程是否存活(isAlive()=this.isAlive()),存活的情况下就 wait。

前面你也说了wait是释放锁和CPU资源的,join()方法是在子线程调用的,wait是Object的方法,直接调用相当于子线程线程调用了this.wait();

所以释放的子线程这个对象的锁,而不是你定义的lock对象,明白了吗?

所以改成这样就可以执行了。

public class JoinReleaseTest {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this){
                    System.out.println("t1能打印,说明释放锁了========");
                }
            }
        },"子线程");

        synchronized (t1) {
            t1.start();
            t1.join();
            System.out.println("main 执行完毕========");
        }

    }
}

对了,你刚刚说你经常用synchronized,等我喝口水,我们继续聊聊synchronized。


  • 原创不易,英雄点个赞再走。
  • 可以关注微信公众号:面试官收题
  • 领取一些我收集的JAVA学习和面试资料
  • 有兴趣讨论问题也可以加我微信:gk6688225