你真的了解JAVA线程吗-Thread

202 阅读11分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

目录

  • JAVA线程
  • JAVA线程状态
  • 通过JAVA代码理解线程状态
  • JAVA线程生命周期
  • Thread的方法
  • init()
  • start()
  • run()
  • sleep(long)、sleep(long,int)
  • stop()
  • interrupt()
  • yield()
  • join()
  • setDaemon()
  • setPriority()

线程

看一下百度百科对 进程线程 的介绍。

进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程:线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。

一个进程可以有很多线程,每条线程并行执行不同的任务

在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。

JAVA线程

在JAVA中,线程以对象的形式存在,我们可以通过操作对象的方式来操作线程。

介绍三种JAVA中创建线程的方式。

1. 实现Runnable

class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

@Test
void testRunnable() {
    new MyRunnable().run();
}

2. 继承Thread

class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

@Test
void testThread() {
    new MyThread().start();
}

3. 实现Callable

class MyCallable implements Callable {

    @Override
    public Object call() throws Exception {
        System.out.println("线程执行");
        return null;
    }
}

@Test
void testCallable() throws Exception {
    new MyCallable().call();
}

JAVA线程状态

Thread类为例,JAVA中线程分为6种状态。

  1. NEW(新建):新创建了一个线程,还未启动(比如上文的new MyThread())。
  2. RUNNABLE(运行):Java线程中将 就绪(READY)和 运行中(RUNNING)两种状态笼统的称为 “运行”状态。 Thread对象创建后,调用了该对象的start()方法(比如上文new MyThread().start())。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(READY)。就绪状态的线程在获得CPU时间片后变为运行中状态(RUNNING)。
  3. BLOCKED(阻塞):线程被锁阻塞。
  4. WAITING(等待):等待,这里是无限等待
  5. TIMED_WAITING(计时等待):等待,在指定时间后返回,不同于WAITING。
  6. TERMINATED(消亡):该线程已经执行完毕。

下面是Thread类内部对状态的定义。

public enum State {
    /**
     * 尚未启动的线程的线程状态。
     */
    NEW,

    /**
     * 可运行线程的线程状态。处于可运行状态的线程正在Java虚拟机中
     * 执行等待来自操作系统的其他资源,如处理器。
     */
    RUNNABLE,

    /**
     * 等待监视器锁而阻塞的线程的线程状态。
     * 处于阻塞状态的线程正在等待监视器锁进入同步块/方法或调用后重新输入同步块/方法
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * 等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     */
    WAITING,

    /**
     * 具有指定等待时间的等待线程的线程状态。
     * 线程处于定时等待状态,这是由于调用下列方法中的一个具有指定的正等待时间:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * 终止线程的线程状态。
     * 线程已完成执行。
     */
    TERMINATED;
}

通过JAVA代码理解线程状态

private static Object lock = new Object();

class MyThread extends Thread {
    @Override
    public void run() {
        synchronized (lock) {
            if("myThread0".equals(Thread.currentThread().getName())){
                try {
                    // 让当前线程进入TIMED_WAITING状态
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else if("myThread1".equals(Thread.currentThread().getName())){
                try {
                    // 让当前线程进入WAITING状态
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
        System.out.println(Thread.currentThread().getName() + "线程执行run()");
    }
}

@Test
void testThread() throws InterruptedException {
    MyThread myThread0 = new MyThread();
    myThread0.setName("myThread0");
    MyThread myThread1 = new MyThread();
    myThread1.setName("myThread1");
    // 此时myThread0和myThread1都处于NEW状态
    System.out.println("第一次打印myThread0状态为:" + myThread0.getState().name());
    System.out.println("第一次打印myThread1状态为:" + myThread1.getState().name());
    myThread0.start();
    myThread1.start();
    // start()方法调用后,会调用run()方法会需要执行时间,所以此处睡眠一会(此处睡眠的时间可适当调整)
    Thread.sleep(200);
    // 睡眠一会后,myThread0和myThread1都执行了run()方法
    // 因为synchronized锁的原因只有一个会进入synchronized里的代码,所以一个为TIMED_WAITING状态,一个为BLOCKED状态
    System.out.println("第二次打印myThread0状态为:" + myThread0.getState().name());
    System.out.println("第二次打印myThread1状态为:" + myThread1.getState().name());
    Thread.sleep(1000);
    // 睡眠一会后myThread0执行完毕变为TERMINATED状态,myThread1获取到锁变为WAITING状态
    System.out.println("第三次打印myThread0状态为:" + myThread0.getState().name());
    System.out.println("第三次打印myThread1状态为:" + myThread1.getState().name());
}

查看执行结果。

第一次打印myThread0状态为:NEW
第一次打印myThread1状态为:NEW
第二次打印myThread0状态为:TIMED_WAITING
第二次打印myThread1状态为:BLOCKED
myThread0线程执行run()
第三次打印myThread0状态为:TERMINATED
第三次打印myThread1状态为:WAITING

JAVA线程生命周期

JAVA线程生命周期.jpg

Thread的方法

接下来跟着Thread的方法源码来深入理解JAVA线程。

init()

new Thread()的时候就会调用该方法。

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

init()源码。

/**
 * 初始化一个线程。
 *
 * @param g 线程组
 * @param target 调用其run()方法的对象
 * @param name 新线程的名称
 * @param stackSize 新线程所需的堆栈大小,或者0表示该参数将被忽略。
 * @param acc 继承AccessControlContext,或者AccessController.getContext()如果为空
 * @param inheritThreadLocals 如果{@code true},则从构造线程继承可继承线程局部变量的初始值
 */
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
                  
    // 如果名称为null,直接异常
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    
    // 名称赋值
    this.name = name;
    
    // 是native修饰的方法,大概就是返回当前正在执行的线程引用(父线程)
    Thread parent = currentThread();
    
    // 获取JAVA安全管理器,防止恶意代码对系统产生影响
    SecurityManager security = System.getSecurityManager();
    
    // 优先初始化传入的threadgroup,然后SecurityManager的threadgroup,最后parent的threadgroup
    if (g == null) {
        /* Determine if it's an applet or not */

        /* If there is a security manager, ask the security manager
           what to do. */
        if (security != null) {
            g = security.getThreadGroup();
        }

        /* If the security doesn't have a strong opinion of the matter
           use the parent thread group. */
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    // 确保当前运行的线程是否有权限修改线程组
    g.checkAccess();

    // 校验权限
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }
    
    // 增加线程组中未启用线程的计数,synchronized修饰
    g.addUnstarted();
    
    // 线程组
    this.group = g;
    
    // 是否守护线程
    this.daemon = parent.isDaemon();
    
    // 优先级
    this.priority = parent.getPriority();
    
    // 加载器
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    
    // 设置优先级
    setPriority(priority);
    
    // 子类线程也能继承父类线程的ThreadLocal
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
            
    // 设置新线程栈大小
    this.stackSize = stackSize;

    // 分配一个线程tid,synchronized修饰,保证唯一
    tid = nextThreadID();
}

start()

NEW状态下可调用start()方法。

/**
 * 使这个线程开始执行;Java虚拟机调用这个线程的run()方法。
 */
public synchronized void start() {
    // 判断是否是NEW状态,不是NEW状态抛IllegalThreadStateException异常
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    // 通知该组线程即将启动,该组未启动线程数递减,synchronized修饰
    group.add(this);

    // 是否启动
    boolean started = false;
    try {
        // 真正启动的方法,是一个native方法,表明它具体是由C/C++来实现
        // 大概就是创建JavaThread对象,创建内核线程,在内核线程中执行run方法
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

private native void start0();

run()

Thread的子类需要重写该方法,实现我们需要的业务逻辑。

/**
 * 如果这个线程是使用一个单独的Runnable run对象构造的,
 * 那么Runnable对象的run方法被调用;否则,该方法不执行任何操作并返回。
 */
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

sleep(long)、sleep(long,int)

使当前执行的线程暂时停止执行,不会放弃监视器的所有权。

/**
 * 使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,
 * 以系统计时器和调度器的精度和准确性。线程不会失去任何监视器的所有权。
 */
public static native void sleep(long millis) throws InterruptedException;


/**
 * 使当前正在执行的线程休眠(暂时停止执行),休眠时间为指定的毫秒数加上指定的纳秒数,
 * 这取决于systemd计时器和调度器的精度和准确性。线程不会失去任何监视器的所有权。
 *
 * @param  millis 以毫秒为单位的睡眠时间
 * @param  nanos 额外的纳秒睡眠
 */
public static void sleep(long millis, int nanos) throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    sleep(millis);
}

stop()

该方法已被废弃

该方法本身就是不安全的。会强制线程停止,解锁已锁定的所有监视器,造成数据不一致问题。建议使用interrupt()方法。

/**
 * 强制线程停止执行。
 * 如果此线程与当前线程不同(即当前线程正在尝试停止与其自身不同的线程),
 * 则安全管理器的checkPermission方法(带有一个调用RuntimePermission("stopThread"参数)加法。
 * 同样,这可能会导致抛出SecurityException(在当前线程中)。
 * 此线程表示的线程将被迫停止其异常操作,并抛出一个新创建的<code>ThreadDeath</code>对象作为异常。
 * 允许停止尚未启动的线程。
 * 如果线程最终启动,它将立即终止。
 * 如果未捕获的异常是ThreadDeath的实例,
 * 则对未捕获异常作出反应的顶级错误处理程序不会打印消息,也不会以其他方式通知应用程序。
 */
@Deprecated
public final void stop() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        checkAccess();
        if (this != Thread.currentThread()) {
            security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
        }
    }
    // 零状态值对应于“NEW”,它不能更改为非NEW,因为我们持有锁。
    if (threadStatus != 0) {
        resume(); // Wake up thread if it was suspended; no-op otherwise
    }

    // VM可以处理所有线程状态
    stop0(new ThreadDeath());
}

interrupt()

给线程发送中断信号,但是不保证线程被真正的中断(不会中断一个正在运行的线程)。

在调用interrupt()方法之前,如果当前线程已经处于阻塞状态(比如调用了sleep(long)方法),那么调用该阻塞线程的interrupt()方法将导致当前线程从sleep(long)函数中醒来,并抛出InterruptedException异常。

/**
 * 中断这个线程。
 */
public void interrupt() {
    // 不是当前线程调用,检验是否有修改权限
    if (this != Thread.currentThread())
        checkAccess();
    // 
    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            // native方法,设置中断标志
            interrupt0();           // 只是为了设置中断标志
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

yield()

向调度器提示当前线程愿意放弃当前使用的处理器,调度器可以忽略这个提示。用于改善线程之间的相互进展

/**
 * 向调度器提示当前线程愿意放弃当前使用的处理器。调度程序可以忽略这个提示。
 */
public static native void yield();

join()

等待线程结束

比如主线程启动了一个子线程,但子线程执行时间长,主线程想要获得子线程的执行结果,主线程可以通过join() 方法加入子线程。

/**
 * 等待线程死亡的时间最多为{@code millis}毫秒。超时{@code o}意味着永远等待。
 */
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;
        }
    }
}

setDaemon()

线程启动之前,可以设置守护线程。

/**
 * 将此线程标记为{@linkplain #isDaemon daemon}线程或用户线程。
 * 当唯一运行的线程都是守护线程时,Java虚拟机退出。
 */
public final void setDaemon(boolean on) {
    checkAccess();
    if (isAlive()) {
        throw new IllegalThreadStateException();
    }
    daemon = on;
}

setPriority()

更改线程优先级。

/**
 * 更改此线程的优先级。
 */
public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}