Java 多线程¶
Thread 和 Runnable¶
JDK提供了Thread
类和Runnable
接口来让我们实现自己的“线程”类。
- 继承
Thread
类,并重写run
方法; - 实现
Runnable
接口的run
方法;
继承 Thread 类¶
public class Demo {
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread");
}
}
public static void main(String[] args) {
Thread myThread = new MyThread();
myThread.start();
}
}
注意要调用start()
方法后,该线程才算启动!
我们在程序里面调用了start()方法后,虚拟机会先为我们创建一个线程,然后等到这个线程第一次得到时间片时再调用run()方法。
注意不可多次调用start()方法。在第一次调用start()方法后,再次调用start()方法会抛出异常。
实现Runnable接口¶
接着我们来看一下Runnable
接口(JDK 1.8 +):
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
可以看到Runnable
是一个函数式接口,这意味着我们可以使用 Java 8 的函数式编程来简化代码。
示例代码:
public class Demo {
public static class MyThread implements Runnable {
@Override
public void run() {
System.out.println("MyThread");
}
}
public static void main(String[] args) {
new MyThread().start();
// Java 8 函数式编程,可以省略MyThread类
new Thread(() -> {
System.out.println("Java 8 匿名内部类");
}).start();
}
}
Thread类构造方法¶
Thread
类是一个Runnable
接口的实现类。
Thread
类的构造方法,简单调用一个私有的init
方法来实现初始化。init
的方法签名:
// Thread类源码
// 片段1 - init方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals)
// 片段2 - 构造函数调用init方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
// 片段3 - 使用在init方法里初始化AccessControlContext类型的私有属性
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
// 片段4 - 两个对用于支持ThreadLocal的私有属性
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
init
方法的这些参数:
-
g:线程组,指定这个线程是在哪个线程组下;
-
target:指定要执行的任务;
-
name:线程的名字,多个线程的名字是可以重复的。如果不指定名字,见片段2;
-
acc:见片段3,用于初始化私有变量
inheritedAccessControlContext
。
这个变量有点神奇。它是一个私有变量,但是在
Thread
类里只有init
方法对它进行初始化,在exit
方法把它设为null
。其它没有任何地方使用它。一般我们是不会使用它的,那什么时候会使用到这个变量呢?可以参考这个stackoverflow的问题:Restrict permissions to threads which execute third party software;
- inheritThreadLocals:可继承的
ThreadLocal
,见片段4,Thread
类里面有两个私有属性来支持ThreadLocal
,我们会在后面的章节介绍ThreadLocal
的概念。
实际情况下,我们大多是直接调用下面两个构造方法:
Thread(Runnable target)
Thread(Runnable target, String name)
Thread类的几个常用方法¶
-
currentThread():静态方法,返回对当前正在执行的线程对象的引用;
-
start():开始执行线程的方法,java虚拟机会调用线程内的run()方法;
-
yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;
-
sleep():静态方法,使当前线程睡眠一段时间;
-
join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;
Thread类与Runnable接口的比较¶
- 由于Java“单继承,多实现”的特性,Runnable接口使用起来更灵活。
- Runnable接口更符合面向对象,将线程单独进行对象的封装。
- Runnable接口耦合性低。
- 如果不需要使用Thread类的诸多方法,使用Runnable接口更为轻量。
所以,我们通常优先使用“实现Runnable
接口”这种方式来自定义线程类。