【Java】JDK源码分析——Thread

一.概述

      Java虚拟机允许应用程序同时运行多个运行线程。
      每个线程都有优先级,优先级高的线程可以被优先执行。当一个线程中创建了另一个新线程,新线程的优先级默认等于这个线程的优先级。只有守护线程创建的线程才是守护线程。
      当一个Java虚拟机启动时,通常会启动一个非守护线程,来运行main方法。虚拟机将继续执行此线程,直到以下情况发生:
      1)调用Runtime类的exit方法,并且安全管理器允许退出操作发生。
      2)除了守护线程之外的所有线程都已经死亡。即线程内代码全部执行完返回,或线程运行时抛出异常。
      每个线程都有一个名字,用于识别线程创建的目的。多个线程可能有相同的名字。若创建了一个没有指定名字的线程,则会自动为其生成一个名字。
Thread.java中的相关代码:

public class Thread implements Runnable {
    // native方法
	private static native void registerNatives();
	// 静态代码块初始化
    static {
        registerNatives();
    }}

1.实现了Runnable接口,可以在线程中运行。
2.在静态代码块中调用registerNatives方法,确保该方法第一个执行。registerNatives方法用于动态注册native方法,将java方法和native方法进行关联。

二.源码解析

1.重要的全局变量

Thread.java中的相关代码:

	private volatile String name; // 用于记录线程的名字

	private int priority; // 用于记录线程的优先级

	private boolean daemon = false; // 用于表示线程是否为守护线程

	private Runnable target; // 用于保存线程要执行的代码

	private ThreadGroup group; // 用于记录线程所在的组

	private ClassLoader contextClassLoader; // 用于保存上下文类加载器

	// 用于根据封装的上下文对系统资源访问做出决策
	private AccessControlContext inheritedAccessControlContext;

	private static int threadInitNumber; // 用于匿名线程的自动编号

	ThreadLocal.ThreadLocalMap threadLocals = null; // 用于保存当前线程共享的对象

	// 用于保存从父线程继承的共享对象
	ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 

	private long stackSize; // 当前线程请求的堆栈大小,默认为0

	private long tid; // 用于保存线程ID

	private static long threadSeqNumber; // 用于自动生成线程ID

	private volatile int threadStatus = 0; // 用于表示线程的状态,0表示线程尚未启动

	// 在可中断I/O操作中阻塞此线程的对象(如果有的话)。
	// 应在设置此线程的中断状态后调用拦截器的中断方法。
	private volatile Interruptible blocker;

	private final Object blockerLock = new Object(); // 用于同步锁

	public final static int MIN_PRIORITY = 1; // 线程最小优先级

	public final static int NORM_PRIORITY = 5; // 线程默认的优先级

	public final static int MAX_PRIORITY = 10; // 线程最大优先级

	// 允许子类重写上下文类加载器权限
	private static final RuntimePermission SUBCLASS_IMPLEMENTATION_PERMISSION =
                    new RuntimePermission("enableContextClassLoaderOverride");
                    

2.内部枚举类State

用来表示线程的状态。
Thread.java中的相关代码:

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

        // 可运行的线程的状态
        // 可能在Java虚拟机中执行,也可能在等在操作系统的其它资源
        RUNNABLE,

        // 获取不到锁的线程的状态
        // 阻塞状态的线程可能正在等待获取锁来执行同步方法或代码块
        // 或在调用wait方法释放锁后,又需要锁来执行同步方法或代码块
        BLOCKED,

        // 等待的线程的状态
        // 等待状态的线程正在等待另一个线程的特定操作
        // 调用wait方法不指定超时时间、调用join方法不指定超时时间,
		// 线程将进入该状态
        // 一个线程调用锁对象的wait方法,需要等待另一个线程调用该锁对象
        // 的notify 或notifyAll方法
        // 一个线程调用join方法,需要等待一个指定的线程终止
        WAITING,

        // 指定了等待时间等待的线程的状态
        // 调用sleep方法、调用wait方法指定超时时间、调用join方法指定超时时间
        // 线程将处于该状态
        TIMED_WAITING,

        // 终止的线程的状态
        // 线程已经执行完毕
        TERMINATED;
	}

3. nextThreadNum方法

当创建线程不指定线程名称时,调用此方法产生编号作为线程名称的一部分。
Thread.java中的相关代码:

	private static synchronized int nextThreadNum() {
    	// 返回,自增
        return threadInitNumber++;
	}

4. nextThreadID方法

用于创建线程ID。
Thread.java中的相关代码:

	private static synchronized long nextThreadID() {
    	// 自增,返回
        return ++threadSeqNumber;
    }

5.常用的构造方法

1)无参数

Thread.java中的相关代码:

	public Thread() {
    	// 调用四个参数的init方法
    	// 默认线程名为”Thread-自增编号“
    	// 堆栈大小0,表示忽略该参数
        init(null, null, "Thread-" + nextThreadNum(), 0);
	}

2)参数为Runnable

Thread.java中的相关代码:

	public Thread(Runnable target) {
    	// 调用四个参数的init方法
    	// 保存target,默认线程名为”Thread-自增编号“
    	// 堆栈大小0,表示忽略该参数
        init(null, target, "Thread-" + nextThreadNum(), 0);
	}

3)参数为String

Thread.java中的相关代码:

	public Thread(String name) {
    	// 调用四个参数的init方法
    	// 堆栈大小0,表示忽略该参数
        init(null, null, name, 0);
	}

4)参数为ThreadGroup、Runnable、String、long

Thread.java中的相关代码:

    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
    	// 调用四个参数的init方法
        init(group, target, name, stackSize);
	}

5)四个参数的init方法

Thread所有的构造方法都调用该方法来实现,用来对线程进行初始化。
Thread.java中的相关代码:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        // 调用五个参数的init方法,默认AccessControlContext为空
        init(g, target, name, stackSize, null);
	}

6)五个参数的init方法

线程初始化的核心实现。相比四个参数的init方法,多了一个AccessControlContext类型的参数。
Thread.java中的相关代码:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        // 若线程名字为空,则抛出异常
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        // 保存线程的名字到全局变量
        this.name = name;

        // 获取创建当前线程的线程,相当于当前线程的父线程
        Thread parent = currentThread();
        // 获取安全管理器,默认是关闭的
        SecurityManager security = System.getSecurityManager();
        // 若线程组为空
        if (g == null) {

            // 若开启了安全管理器
            if (security != null) {
                // 获取安全管理器的线程组
                g = security.getThreadGroup();
            }

            // 若安全管理器没有配置线程组
            if (g == null) {
                // 获取当前线程父线程的线程组
                g = parent.getThreadGroup();
            }
        }

        // 检查权限,是否允许访问
        g.checkAccess();

        // 若开启了安全管理器
        if (security != null) {
            // 若当前类(可能是子类)的实例可以在不违反安全约束的条件下创建
            if (isCCLOverridden(getClass())) {
                // 安全管理器检查权限是否允许子类重写上下文类加载器
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        // 通知线程组,增加一个未开始的线程
        g.addUnstarted();

        // 保存线程组到全局变量
        this.group = g;
        // 判断父线程是否为守护线程,将结果保存到全局变量
        this.daemon = parent.isDaemon();
        // 当前线程的优先级和父线程的优先级相同
        this.priority = parent.getPriority();
        // 若安全管理器没有开启,
		// 或父线程的类的实例可以在不违反安全约束的条件下创建
        if (security == null || isCCLOverridden(parent.getClass()))
            // 调用方法,将父线程的上下文类加载器保存到全局变量

            // 因为当父线程的类可以不违法安全约束创建实例,说明他可能存在子类,
            // 即子类化。由于多态,调用getContextClassLoader可能由子类实现
            this.contextClassLoader = parent.getContextClassLoader();
        else // 若安全管理器开启,并且创建父线程的类的实例违反安全约束
            // 调用静态变量,将父线程的上下文类加载器保存到全局变量

            // 因为创建父线程类的实例违反安全规约,所以不可能存在子类
            // 因此直接从父类的静态变量中获取
            this.contextClassLoader = parent.contextClassLoader;

        // 若存取控制上下文为空,从AccessController中获取
        // 否则直接保存到全局变量
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        // 保存线程需要执行的Runnable对象
        this.target = target;
        // 设置线程优先级
        setPriority(priority);
        // 若父线程中存在继承的ThreadLocal
        if (parent.inheritableThreadLocals != null)
            // 则根据父进程中继承的ThreadLocal创建一个ThreadLocalMap对象
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        // 保存堆栈大小到全局变量
        this.stackSize = stackSize;

        // 设置线程ID
        tid = nextThreadID();
    }

6. currentThread方法

获取当前执行的线程。
Thread.java中的相关代码:

	public static native Thread currentThread();

7. setPriority方法

设置线程优先级。
Thread.java中的相关代码:

	public final void setPriority(int newPriority) {
        ThreadGroup g;
        // 检查当前运行的线程是否拥有修改此线程的权限
        // 详解在1)处
        checkAccess();
        // 若要设置的线程优先级超过最大优先级或小于最小优先级,则抛出异常
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        // 获取线程组,若线程组不为空
        // 详解在2)处
        if((g = getThreadGroup()) != null) {
            // 若要设置的优先级超过了线程组的最大优先级
            if (newPriority > g.getMaxPriority()) {
                // 将要设置的优先级改为线程组的最大优先级
                newPriority = g.getMaxPriority();
            }
            // 将优先级保存到全局变量
            // 调用setPriority0方法设置线程优先级
            // 详解在3)处
            setPriority0(priority = newPriority);
        }
    }

1)checkAccess方法

检查当前运行的线程是否拥有修改此线程的权限。
Thread.java中的相关代码:

	public final void checkAccess() {
    	// 获取安全管理器
        SecurityManager security = System.getSecurityManager();
        // 若开启了安全管理
        if (security != null) {
            // 通过安全管理器检查当前运行的线程是否拥有修改此线程的权限
            security.checkAccess(this);
        }
	}

2)getThreadGroup方法

获取线程组。
Thread.java中的相关代码:

	public final ThreadGroup getThreadGroup() {
        // 返回全局变量
        return group;
    }

3)setPriority0方法

设置线程优先级的核心方法。
Thread.java中的相关代码:

	private native void setPriority0(int newPriority);

8. start方法

启动线程。
调用该方法将使JVM执行线程的run方法。
Thread.java中的相关代码:

	public synchronized void start() {
        // 若当前线程的状态不为0,即不是新创建尚未启动的状态
        if (threadStatus != 0)
            // 则抛出异常
            throw new IllegalThreadStateException();

        // 通知线程组,该线程已启动
        group.add(this);

        // 用于判断启动状态,设置false
        boolean started = false;
        try {
            // 启动线程
            // 详解在1)处
            start0();
            // 设置true
            started = true;
        } finally {
            // 若发生异常
            try {
                // 若线程没有启动
                if (!started) {
                    // 通知线程组线程启动失败
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                // 若start0方法抛出异常,则什么都不做,它的值会传给调用的堆栈。
            }
        }
    }

1)start0方法

线程启动的核心方法。
Thread.java中的相关代码:

	private native void start0();

9. run方法

线程执行的核心方法。
若该线程通过传入Runnable对象创建,则调用Runnable的run方法。
若该线程通过继承Thread类实现,则需要重写Run方法。
Thread.java中的相关代码:

    @Override
	public void run() {
    	// 若target不为空,即线程通过传入Runnable对象创建
        if (target != null) {
            // 调用run方法
            target.run();
        }
    }

10. exit方法

该方法被系统调用,用于在线程退出前,对线程进行清理。
Thread.java中的相关代码:

	private void exit() {
    	// 若线程组不为空
        if (group != null) {
            // 通知线程组
            group.threadTerminated(this);
            // 释放引用
            group = null;
        }
        // 释放Runnable对象
        target = null;
        // 释放其它资源
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

11. interrupt方法

用于中断一个线程。
Thread.java中的相关代码:

	public void interrupt() {
    	// 若调用该方法的线程不是本线程
        if (this != Thread.currentThread())
            checkAccess(); // 检查权限,是否允许访问

        // 同步锁
        synchronized (blockerLock) {
            // 获取中断拦截器
            Interruptible b = blocker;
            // 若中断拦截器不为空
            if (b != null) {
                // 设置线程中断标志位
                // 详解在1)处
                interrupt0();
                // 调用中断拦截器的方法
                b.interrupt(this);
                // 返回
                return;
            }
        }
        // 若没有中断拦截器,直接设置线程中断标志位
        interrupt0();
	}

1)interrupt0方法

中断核心方法,该方法会设置线程中断标志位。
Thread.java中的相关代码:

	private native void interrupt0();

12. interrupted方法

判断当前线程是否中断。
调用该方法将清除线程的中断标志位。
Thread.java中的相关代码:

	public static boolean interrupted() {
    	// 获取当前线程,调用isInterrupted方法判断
        return currentThread().isInterrupted(true);
    }

13. isInterrupted方法

判断调用该方法的线程对象是否中断

1)无参数

Thread.java中的相关代码:

	public boolean isInterrupted() {
    	// 调用了重载方法
        return isInterrupted(false);
    }

2)参数为boolean

判断线程中断的核心方法。
中断状态的将被重置。重置的值不取决于参数ClearInterrupted。
Thread.java中的相关代码:

	private native boolean isInterrupted(boolean ClearInterrupted);

14. isAlive方法

判断线程是否存活。
存活即线程开始运行但并没有死亡的期间。
Thread.java中的相关代码:

	public final native boolean isAlive();

15. getPriority方法

获取线程优先级。
Thread.java中的相关代码:

	public final int getPriority() {
    	// 直接返回
        return priority;
    }

16. setName方法

设置线程的名字。
Thread.java中的相关代码:

	public final synchronized void setName(String name) {
    	// 检查权限,是否允许访问
        checkAccess();
        // 若名字为空,则抛出异常
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        // 将线程名保存到全局变量
        this.name = name;
        // 若线程不是新创建未运行的状态
        if (threadStatus != 0) {
            // 通知native层处理
            // 详解在1)处
            setNativeName(name);
        }
    }

1)setNativeName方法

设置线程名字的核心方法。
Thread.java中的相关代码:

	private native void setNativeName(String name);

17. getName方法

获取线程的名字。
Thread.java中的相关代码:

	public final String getName() {
        // 直接返回
        return name;
    }

18. getThreadGroup方法

获取线程组。
Thread.java中的相关代码:

	public final ThreadGroup getThreadGroup() {
    	// 直接返回
        return group;
    }

19. activeCount方法

返回当前线程的线程组及其子线程组中所有活跃线程的数量。
Thread.java中的相关代码:

	public static int activeCount() {
    	// 获取当前线程,获取线程组,遍历统计
        return currentThread().getThreadGroup().activeCount();
    }

20. join方法

1)不指定超时时间

当前线程等待在当前线程中调用该方法的线程死亡。
Thread.java中的相关代码:

	public final void join() throws InterruptedException {
    	// 调用重载方法,超时时间为0
        join(0);
    }

2)指定超时时间

若在指定时间内调用该方法的线程没有执行完毕,则等待的线程将不再等待。
Thread.java中的相关代码:

    public final synchronized void join(long millis, int nanos)
    throws InterruptedException {

        // 若指定毫秒小于0,则抛出异常
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        // 若纳秒小于0或大于999999,则抛出异常
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        // 若纳秒超过了500000,则化零为整,毫秒加一
        // 若纳秒不为零且毫秒为0,则设置最小时间1毫秒
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        // 调用重载方法
        join(millis);
	}

重载的join方法。
最多等待mills毫秒来让线程死亡,参数为0表示永久等待。
Thread.java中的相关代码:

    public final synchronized void join(long millis)
	throws InterruptedException {
    	// 获取系统当前时间作为时间基准
        long base = System.currentTimeMillis();
        // 用于计算当前时间到base时间的间隔
        long now = 0;

        // 若等待时间小于0,则抛出异常
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        // 若等待时间为0,说明是永久等待
        if (millis == 0) {
            // 循环,若调用join方法的线程活跃
            while (isAlive()) {
                // 调用join方法所在的线程永久等待
                wait(0);
            }
        } else { // 不是永久等待
            // 循环,若调用join方法的线程活跃
            while (isAlive()) {
                // 计算还需要等待的时长
                long delay = millis - now;
                // 若还需要等待的时长小于0,则跳出循环
                if (delay <= 0) {
                    break;
                }
                // 调用join方法所在的线程等待delay毫秒,超时放弃等待
                wait(delay);
                // 计算目前已经等待的时间
                now = System.currentTimeMillis() - base;
            }
        }
	}

注意:如在线程A中调用线程B的join方法,B为调用join方法的线程,A为调用join方法所在的线程。由于join方法中调用了wait方法,因此线程A阻塞等待,线程B正常执行。

21. setDaemon方法

用于标记一个线程是守护线程还是用户线程。
当唯一运行的线程为守护线程时,Java虚拟机退出。
该方法必须在线程开始前调用。
Thread.java中的相关代码:

	public final void setDaemon(boolean on) {
    	// 检查权限,是否允许访问
        checkAccess();
        // 若线程活跃,即已经开始未结束,则抛出异常
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        // 设置标志位
        daemon = on;
    }

22. isDaemon方法

判断线程是否为守护线程。
Thread.java中的相关代码:

	public final boolean isDaemon() {
    	// 直接返回
        return daemon;
    }

23. toString方法

返回线程对应的字符串形式。
Thread.java中的相关代码:

	public String toString() {
    	// 获取线程组
        ThreadGroup group = getThreadGroup();
        // 若线程组存在
        if (group != null) {
            // 返回线程名,线程优先级,线程组名称
            return "Thread[" + getName() + "," + getPriority() + "," +
                           group.getName() + "]";
        } else { // 若线程组不存在
            // 返回线程名,线程优先级
            return "Thread[" + getName() + "," + getPriority() + "," +
                            "" + "]";
        }
    }

24. getContextClassLoader方法

获取线程的上下文类加载器。
Thread.java中的相关代码:

	// 由于代码中调用getCallerClass方法,所以需要加上该注解
	// 详解在1)处
    @CallerSensitive
	public ClassLoader getContextClassLoader() {
    	// 若线程上下文类加载器为空,则返回null
        if (contextClassLoader == null)
            return null;
        //获取安全管理器
        SecurityManager sm = System.getSecurityManager();
        // 若开启了安全管理器
        if (sm != null) {
            // 检查类加载器的权限
            ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                                   Reflection.getCallerClass());
        }
        // 返回上下文类加载器
        return contextClassLoader;
    }

1)CallerSensitive注解

      jvm的开发者认为jdk内有一些方法是危险的,不希望开发者调用,因此将这些方法通过CallerSensitive注解修饰。
      所有内部调用getCallerClass方法的方法都必须使用@CallerSensitive进行注解,该方法用来获取调用该方法的类。在获取调用该方法的类时,会跳过调用链路上所有被@CallerSensitive直接修饰的方法所在的类,避免了多层反射调用时产生不安全的隐患。

25. setContextClassLoader方法

为线程设置上下文类加载器。
Thread.java中的相关代码:

	public void setContextClassLoader(ClassLoader cl) {
    	// 获取安全管理器
        SecurityManager sm = System.getSecurityManager();
        // 若开启了安全管理器
        if (sm != null) {
            // 检查权限,是否允许设置上下文类加载器
            sm.checkPermission(new RuntimePermission("setContextClassLoader"));
        }
        // 保存到全局变量
        contextClassLoader = cl;
    }

26. holdsLock方法

判断当前线程是否持有某个对象的锁。
Thread.java中的相关代码:

	public static native boolean holdsLock(Object obj);

27. getId方法

获取线程ID。
Thread.java中的相关代码:

    public long getId() {
        return tid;
	}

28. getState方法

获取当前线程的状态。
该方法用于监控系统状态,而不是用于同步控制。
Thread.java中的相关代码:

    public State getState() {
        return sun.misc.VM.toThreadState(threadStatus);
	}

29. yield方法

该方法用于提示线程调度程序,当前线程愿意放弃当前处理器的使用。
该方法用来缓解线程对CPU的过度使用。
Thread.java中的相关代码:

	public static native void yield();

30. sleep方法

当前正在执行的线程,在指定的时间内暂停执行。
线程不释放持有的锁。
Thread.java中的相关代码:

    public static void sleep(long millis, int nanos)
	throws InterruptedException {
    	// 若毫秒小于0,则抛出异常
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        // 若纳秒小于0或大于999999,则抛出异常
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        // 若纳秒超过了500000,则化零为整,毫秒加一
        // 若纳秒不为零且毫秒为0,则设置最小时间1毫秒
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        // 调用重载方法
        sleep(millis);
    }

调用重载的sleep方法。
Thread.java中的相关代码:

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

31. stop方法

用于强制停止线程的执行。
弃用原因:线程在被停止时,停止的线程会突然释放所有持有的锁。之前等待这些锁的对象,可能会产生不确定的行为。
Thread.java中的相关代码:

    // 该方法已被弃用,不建议使用
    @Deprecated
	public final void stop() {
    	// 获取安全管理器
        SecurityManager security = System.getSecurityManager();
        // 若开启了安全管理器
        if (security != null) {
            // 检查权限,是否允许访问
            checkAccess();
            // 若在一个线程中停止另一个线程
            if (this != Thread.currentThread()) {
                // 检查权限
                security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
            }
        }
        // 若线程的状态不是新创建未运行的状态
        if (threadStatus != 0) {
            // 若线程被挂起,唤醒线程
            resume(); 
        }

        // 停止线程运行
        // 详解在1)处
        stop0(new ThreadDeath());
    }

1)stop0方法

停止线程的核心方法。
Thread.java中的相关代码:

	private native void stop0(Object o);

32. suspend方法

暂停线程的执行,即将线程挂起。
若线程是活跃的,则将线程被挂起,直到线程被恢复,否则不会继续执行。
弃用原因:容易造成死锁。当线程被挂起时持有系统关键资源的锁,则在它被恢复之前,其它线程不能访问该资源。
Thread.java中的相关代码:

    // 该方法已被弃用,不建议使用
    @Deprecated
	public final void suspend() {
    	// 检查权限,是否允许访问
        checkAccess();
        // 挂起线程
        // 详解在1)处
        suspend0();
    }

1)suspend0方法

线程挂起的核心方法。
Thread.java中的相关代码:

	private native void suspend0();

33. resume方法

恢复暂停(挂起)的线程。
弃用原因:此方法需要和suspend方法配合使用,因为suspend方法易造成死锁,被弃用,所以该方法也被弃用。
Thread.java中的相关代码:

    // 该方法已被弃用,不建议使用
    @Deprecated
	public final void resume() {
    	// 检查权限,是否允许访问
        checkAccess();
        // 恢复线程
        // 详解在1)处
        resume0();
    }

1)resume0方法

恢复挂起线程的核心方法。
Thread.java中的相关代码:

	private native void resume0();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值