虽然多线程相关的面试题比较基础,但是却是高频面点。
这里写一篇文章总结一下,毕竟,“纸上得来终觉浅,绝知此事要躬行”。
所属不同:start()
方法是 Thread
类自己声明的方法,run()
方法是 Thread
类实现自 Runnable
接口的;
定位不同:在 Thread
对象上调用 start()
方法才能创建并开启线程,正因为调用了 start()
方法,线程才从无到有,从有到启动。在 start()
方法内部会调用 start0()
这个 native
方法。而 run()
方法仅仅是封装了需要执行的代码,这是一个普通方法都有的功能。
调用不同:在开启线程时,start()
方法是需要手动调用的,而 run()
方法是由虚拟机调用的。
先看演示代码:
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()
方法不是必须写在 thread2
的 run()
方法之中。看下面的例子:
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.
并行(Parallel)是“并排行走”或“同时实行”,某个时间点,在计算机操作系统中指,一组程序按照独立异步的速度执行,无论是微观还是宏观,程序都是一起执行的。
并发(Concurrent),是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
简言之,
并发,是说多个任务,在同一时间段内都发生了;
并行,是说多个任务,在同一时间点上都发生了。
不能,Java是做不到的,唯一能够去干预的就是C语言调用内核的API去指定才行。
CPU 是为了迎合操作系统的多线程从而提高系统的计算效率。但是具体分配任务到各个内核中去执行的并非 JAVA 与 JVM 而是操作系统,也就是说,你所执行的多线程,可能会被分配到同一个CPU内核中运行。也可能非配到不同的 CPU 中运行。如果可以控制CPU的分配,那也应该是操作系统的 api 才能实现的了。
考虑 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_PRIORITY
,NORM_PRIORITY
和 MIN_PRIORITY
三种级别。
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);
}
wait()
方法释放 CPU 执行权,并且释放锁;sleep()
方法释放 CPU 执行权,不释放锁。sleep()
方法可以在任何地方调用;wait()
方法只能在同步中调用,如果不在同步中调用,会抛出 IllegalMonitorStateException
。不要使用 Thread
的 stop()
方法,该方法已被标记为 @Deprecated
。官方推荐我们使用 Thread
的 interrupt()
方法来发出中断信号,这是一种协作式的中断。
看下面的演示代码:
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
调用 Thread.yield()
方法:
public static native void yield();
需要注意的是,调用这个方法只是给调度器发出一个信号,当前线程愿意让出 CPU 的执行权。但是,调度器可以忽略掉这个信号。
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
希望本文能够加深同学们对多线程的学习。