Skip to content

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接口”这种方式来自定义线程类。