Java多线程不可不会的面试题

1.前言

虽然多线程相关的面试题比较基础,但是却是高频面点。

这里写一篇文章总结一下,毕竟,“纸上得来终觉浅,绝知此事要躬行”。

2.正文

2.1 Thread 类中的 start() 方法和 run() 方法有什么区别?

所属不同:start() 方法是 Thread 类自己声明的方法,run() 方法是 Thread 类实现自 Runnable 接口的;
定位不同:在 Thread 对象上调用 start() 方法才能创建并开启线程,正因为调用了 start() 方法,线程才从无到有,从有到启动。在 start() 方法内部会调用 start0() 这个 native 方法。而 run() 方法仅仅是封装了需要执行的代码,这是一个普通方法都有的功能。
调用不同:在开启线程时,start() 方法是需要手动调用的,而 run() 方法是由虚拟机调用的。

2.2 如何控制线程的执行顺序?

先看演示代码:

class Task1 implements Runnable {
    public Task1() {
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is doing task.");
    }
}

public class Demo {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Task1(), "Thread " + i);
            thread.start();
        }
        System.out.println(Thread.currentThread().getName() + " is doing task.");
    }
}

运行一下,看执行结果:

main is doing task.
Thread 1 is doing task.
Thread 0 is doing task.
Thread 2 is doing task.
Thread 4 is doing task.
Thread 3 is doing task.

多次运行,线程的执行顺序并不一致。
我们可以使用 Thread 类的 join() 方法来控制线程的执行顺序。

class Task2 implements Runnable {
    private Thread joiner;
    public Task2(Thread joiner) {
        this.joiner = joiner;
    }
    @Override
    public void run() {
        try {
            joiner.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " is doing task.");
    }
}
public class JoinDemo {
    public static void main(String[] args) {
        Thread joiner = Thread.currentThread();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Task2(joiner), "Thread " + i);
            thread.start();
            joiner = thread;
        }
        System.out.println(Thread.currentThread().getName() + " is doing task.");
    }
}

多次执行程序,可以打印同样的信息:

main is doing task.
Thread 0 is doing task.
Thread 1 is doing task.
Thread 2 is doing task.
Thread 3 is doing task.
Thread 4 is doing task.

join() 方法的含义是在 thread2 上调用 thread1.join()thread2 必须等待 thread1 运行完毕再继续 thread2 的运行。
需要注意的是,调用 thread1.join() 方法不是必须写在 thread2run() 方法之中。看下面的例子:

class Task3 implements Runnable {
    public Task3() {
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is doing task.");
    }
}

public class JoinDemo2 {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Task3(), "Thread " + i);
            thread.start();
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " is doing task.");
    }
}

打印结果:

Thread 0 is doing task.
Thread 1 is doing task.
Thread 2 is doing task.
Thread 3 is doing task.
Thread 4 is doing task.
main is doing task.

2.3 如何区分多线程中的并行和并发?

并行(Parallel)是“并排行走”或“同时实行”,某个时间点,在计算机操作系统中指,一组程序按照独立异步的速度执行,无论是微观还是宏观,程序都是一起执行的。
并发(Concurrent),是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

简言之,

并发,是说多个任务,在同一时间段内都发生了;
并行,是说多个任务,在同一时间点上都发生了。

2.4 在 Java 中能不能指定 CPU 去执行某个线程?

不能,Java是做不到的,唯一能够去干预的就是C语言调用内核的API去指定才行。

CPU 是为了迎合操作系统的多线程从而提高系统的计算效率。但是具体分配任务到各个内核中去执行的并非 JAVA 与 JVM 而是操作系统,也就是说,你所执行的多线程,可能会被分配到同一个CPU内核中运行。也可能非配到不同的 CPU 中运行。如果可以控制CPU的分配,那也应该是操作系统的 api 才能实现的了。

2.5 在项目开发过程中,你会考虑 Java 线程优先级吗?

考虑 Java 线程的优先级,但是不依靠 Java 线程的优先级。

优先级低的线程仅仅是执行的频率较低,而不是说优先级低的线程一定比优先级高的线程后执行。

在 Java 中,有 10 个优先级:

/**
 * The minimum priority that a thread can have.
 */
public final static int MIN_PRIORITY = 1;
/**
 * The default priority that is assigned to a thread.
 */
public final static int NORM_PRIORITY = 5;
/**
 * The maximum priority that a thread can have.
 */
public final static int MAX_PRIORITY = 10;

但是它与多数操作系统都不能映射得很好。比如,Windows 有 7 个优先级且是不固定的。
比较可靠的是当调整优先级的时候,只是用 MAX_PRIORITYNORM_PRIORITYMIN_PRIORITY 三种级别。

2.6 sleep() 方法和 wait() 方法有什么区别?

  • 所属不同:sleep()Thread 类的静态方法,wait()Object 类中的方法
    // Thread 类中
    public static native void sleep(long millis) throws InterruptedException;
    // Object 类中
    public final native void wait(long timeout) throws InterruptedException;
    
    public final void wait() throws InterruptedException {
        wait(0);
    }
    
  • 在同步中,对 CPU 的执行权和锁的处理不同:wait() 方法释放 CPU 执行权,并且释放锁;sleep() 方法释放 CPU 执行权,不释放锁。
  • 调用场合不同:sleep() 方法可以在任何地方调用;wait() 方法只能在同步中调用,如果不在同步中调用,会抛出 IllegalMonitorStateException

2.7 在 Java 中,如何中断线程的执行?

不要使用 Threadstop() 方法,该方法已被标记为 @Deprecated。官方推荐我们使用 Threadinterrupt() 方法来发出中断信号,这是一种协作式的中断。

看下面的演示代码:

public class UseInterrupt2 {
    private static class MyThread extends Thread {
        @Override
        public void run() {
            while (!isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + " is running, " +
                        "isInterrupted() = " + isInterrupted());
            }
            System.out.println(Thread.currentThread().getName() + " end, " +
                    "isInterrupted() = " + isInterrupted());
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 在 main 线程中,调用 myThread 的 interrupt() 方法,发出中断 myThread 线程的信号
        myThread.interrupt();
    }
}

打印结果:

Thread-0 is running, isInterrupted() = false
Thread-0 is running, isInterrupted() = false
Thread-0 is running, isInterrupted() = false
Thread-0 is running, isInterrupted() = false
Thread-0 end, isInterrupted() = true

2.8 如何让当前线程让出 CPU 的执行权?

调用 Thread.yield() 方法:

public static native void yield();

需要注意的是,调用这个方法只是给调度器发出一个信号,当前线程愿意让出 CPU 的执行权。但是,调度器可以忽略掉这个信号。

2.9 sleep(),wait(),到底那个函数才会清除中断标记?

sleep() 方法会清除中断标记:

public class UseInterrupt3 {
    private static class MyThread extends Thread {
        @Override
        public void run() {
            while (!isInterrupted()) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().getName() + " sleep interrupted, " +
                            "isInterrupted() = " + isInterrupted());
                }
                System.out.println(Thread.currentThread().getName() + " is running, " +
                        "isInterrupted() = " + isInterrupted());
            }
            System.out.println(Thread.currentThread().getName() + " end, " +
                    "isInterrupted() = " + isInterrupted());
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 在 main 线程中,调用 myThread 的 interrupt() 方法,发出中断 myThread 线程的信号
        myThread.interrupt();
    }
}

运行会进入一直打印:

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.java.advanced.features.concurrent.stopthread.interrupt.UseInterrupt3$MyThread.run(UseInterrupt3.java:9)
Thread-0 sleep interrupted, isInterrupted() = false
Thread-0 is running, isInterrupted() = false
Thread-0 is running, isInterrupted() = false
Thread-0 is running, isInterrupted() = false
Thread-0 is running, isInterrupted() = false
Thread-0 is running, isInterrupted() = false
...// 省略了无限打印 Thread-0 is running, isInterrupted() = false

wait() 方法也会清除中断标记:

public class UseInterrupt4 {
    private static class MyThread extends Thread {
        private Object lock = new Object();
        @Override
        public  void run() {
            synchronized (lock) {
                while (!isInterrupted()) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        System.out.println(Thread.currentThread().getName() + " wait interrupted, " +
                                "isInterrupted() = " + isInterrupted());
                    }
                    System.out.println(Thread.currentThread().getName() + " is running, " +
                            "isInterrupted() = " + isInterrupted());
                }
                System.out.println(Thread.currentThread().getName() + " end, " +
                        "isInterrupted() = " + isInterrupted());
            }

        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 在 main 线程中,调用 myThread 的 interrupt() 方法,发出中断 myThread 线程的信号
        myThread.interrupt();
    }
}

打印日志:

java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.java.advanced.features.concurrent.stopthread.interrupt.UseInterrupt4$MyThread.run(UseInterrupt4.java:11)
Thread-0 wait interrupted, isInterrupted() = false
Thread-0 is running, isInterrupted() = false

3.最后

希望本文能够加深同学们对多线程的学习。

参考

猜你喜欢

转载自blog.csdn.net/willway_wang/article/details/106031291