02.Java 多线程 - Thread

基本概念

进程 & 线程的概念:

  • 进程:在某种程度上表示相互隔离的、独立运行的程序。

  • 线程:也称作轻量级进程。就象进程一样,线程在程序中是独立的、并发的执行路径,每个线程有它自己的堆栈、自己的程序计数器和自己的局部变量。

进程 & 线程的区别:

  • 与分隔的进程相比,进程中的线程之间的隔离程度要小。

  • 进程可以支持多个线程,它们看似同时执行,但互相之间并不同步。一个进程中的多个线程共享相同的内存地址空间,这就意味着它们可以访问相同的变量和对象,而且它们从同一堆中分配对象。

在 Java 中,用 Thread 类表示线程。


线程状态

在 Thtead 的内部有个枚举类,它定义了线程的状态:

public enum State {
    NEW, // 新建状态
    RUNNABLE, // 就绪状态(即可执行状态)
    BLOCKED, // 阻塞状态
    WAITING, // 等待状态
    TIMED_WAITING, // 等待状态(有时间限制)
    TERMINATED; // 死亡状态
}

1.新建状态

NEW ,指线程刚创建, 尚未启动


2.就绪状态

RUNNABLE ,表示线程可以正常参与竞争 CPU 资源,成功率与其优先级高低有关。


3.阻塞状态

BLOCKED,表示线程进入阻塞并监视锁的状态。

它存在于多个线程有同步操作的场景。比如正在等待另一个线程的 synchronized 块的执行释放。


4.等待状态

WAITING ,该状态下的线程表示正在等待另外一个线程的动作。

例如:

  • 调用了 wait 方法且没有超时,进入等待状态,等待另一个线程调用 notify/notiyfAll 方法。
  • 调用了 join 方法且没有超时 ,进入等待状态,等待另一个线程终止。
  • 调用了 LockSupport.park ,进入等待状态,等待另一个线程调用 LockSupport.park。

5.有时间限制的等待状态

TIMED_WAITING ,存在于:

  • 调用了 wait / wait(long) 方法且没有超时
  • 调用了 join 方法且没有超时
  • 调用了 LockSupport.parkNanos/LockSupport.parkUntil 方法

6.终止状态

  • TERMINATED ,表示线程已完成执行。

线程优先级

线程的优先级具有以下特点:

  • 优先级代表获取 CPU 资源的概率,优先级高的线程获取的概率较大。

  • 优先级无法保证线程的执行次序,因为这个一个概率问题。

  • 线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认的优先级为5。

  • 在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。

在 Thhtead 类中,定义了三个优先级常量:

// 最小
public final static int MIN_PRIORITY = 1;

// 默认
public final static int NORM_PRIORITY = 5;

// 最大
public final static int MAX_PRIORITY = 10;

源码分析

1.线程创建

在 Java 中,用 Thread 表示线程类,它实现了 Runnbale 接口。

首先来看它的签名:

// 签名
public class Thread implements Runnable

// 接口
public interface Runnable {
    public abstract void run();
}

再来看它的部分构造函数:

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

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

public Thread(String name) {
    init(null, null, name, 0);
}

public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    init(g, target, name, stackSize, null);
}

// 关键
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc){...}

从代码可知,构建线程的真正过程在 init 方法中实现。构建一个线程所需要的必要参数有:

  • ThreadGroup :线程组,表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。

  • target:目标,即运行内容,可通过 run 方法执行。

  • name:线程名称,默认是 Thread-id,每个线程都不有不同的线程名。

  • stackSize:开辟新增线程所需的栈空间,为 0 表示忽略该参数。

  • AccessControlContext :上下文。


2.线程就绪

构建一个线程后,可以通过 start 方法来启动它,同时也表示线程进入就绪状态。

同样的,也可以同调用 Thread 的 yeid 方法来已经运行线程重新进入就绪状态。

  • yield

    public static native void yield();

  • start

    // 线程状态,默认为 0,表示线程还未启动,即 NEW 状态
    private volatile int threadStatus = 0;
    
    // 表示线程的线程组
    private ThreadGroup group;
    
    public synchronized void start() {
    
        if (threadStatus != 0){
            //抛出异常...
        }
    
        // 加入线程组
        group.add(this);
    
        boolean started = false;
        try {
            // 关键 
            start0();
            started = true;
        } finally {
            try {
                // 启动失败,通知线程组
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                // do nothing...
            }
        }
    }
    
    // 使用了本地调用,通过 C 代码初始化线程需要的系统资源。
    private native void start0();

3.线程运行

线程在启动之后,会自动调用 run 方法,执行规定好的内容。

private Runnable target;
public void run() {
    if (target != null) {
        target.run();
    }
}

由代码可知,线程在启动后会自动执行 Runnable.run 定义的内容。


4.线程中断

线程中断,并不是指中断线程的运行,而是指设置线程的中断标记位

在 Thread 类中,有三个方法跟中断标记位有关:

public static void main(String[] args) {
    // interrupt:设置线程的中断状态
    Thread.currentThread().interrupt();

    // interrupted:返回线程的上次的中断状态,并清除中断状态
    System.out.println(Thread.currentThread().interrupted());

    // isInterrupt:返回线程的上次的中断状态
    System.out.println(Thread.currentThread().isInterrupted());
}

下面来看它们的具体实现:

  • interrupt,设置线程的中断状态
// 用于同步,表示锁对象
private final Object blockerLock = new Object();

private volatile Interruptible blocker;

public void interrupt() {
    if (this != Thread.currentThread()){
        // 检查系统访问权限
        checkAccess();
    }

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            // 关键 -> 调用本地方法,设置线程中断标志位
            interrupt0(); 
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

// 设置线程中断标志位
private native void interrupt0();

  • interrupted,返回线程的上次的中断状态,并清除中断状态
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

public static native Thread currentThread();

// 入参表示是否清除线程中断标记位
private native boolean isInterrupted(boolean ClearInterrupted);
  • isInterrupt,返回线程的上次的中断状态
public boolean isInterrupted() {
    return isInterrupted(false);
}


5.线程阻塞

若想要令线程进入阻塞状态,可以调用 Thread 类的 sleep 方法实现。

public static void sleep(long millis, int nanos) throws InterruptedException {
    if (millis < 0) {
        // 抛出异常...
    }
    if (nanos < 0 || nanos > 999999) {
        // 抛出异常...
    }
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    sleep(millis);
}

public static native void sleep(long millis) throws InterruptedException;

6.线程等待

在 Thread 类中,想要让线程进入等待,可以调用 join 方法实现。

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(3000);
            System.out.println("t1 完成工作");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
t1.start();

// 关键把 t1 线程加入到当前线程,等待 t1 执行结束,main 才能继续执行
t1.join();
System.out.println("main 完成工作");

下面再来看看它的具体实现:

public final void join() throws InterruptedException {
    join(0);
}

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        // 抛出异常...
    }

    if (millis == 0) {
        // 关键 -> 线程(指代例子中 t1)存活,则(main)进入等待
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

// 判断线程是否存活
public final native boolean isAlive();

这里同样有个疑问,既然 mian 进入等待,那么在 t1 结束后如何被唤醒?

正常情况下进入 wait 的 线程,需要其他线程调用 notify/notifyAll 来唤醒。答案在 t1 线程结束后在 JVM 中会调用 notify 方法:

void JavaThread::exit(bool destroy_vm, ExitType exit_type) ;

// 确保 join 函数
ensure_join(this);

static void ensure_join(JavaThread* thread) {

    Handle threadObj(thread, thread->threadObj());

    ObjectLocker lock(threadObj, thread);

    thread->clear_pending_exception();

    java_lang_Thread::set_thread_status(threadObj(), 
        java_lang_Thread::TERMINATED);

    java_lang_Thread::set_thread(threadObj(), NULL);

    // 关键 -> 调用线程的 notifyAll 方法。
    lock.notify_all(thread);

    thread->clear_pending_exception();
}

你可能感兴趣的:(Java,多线程,Java,Thread,Runable,线程,并发)