请说一下Java Thread对象有哪些属性和方法?

1,843 阅读10分钟

2.1 简介

本文主要从Thread源码层面,整体认识一下java线程。一个线程就是一段程序执行流。在java中用java.lang.Thread类表示:

public class Thread implements Runnable {
    //...
}

java.lang.Runnable:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

run()方法就代表了程序执行流,即线程执行体,该方法由实现类实现。注意:Thread类本身关于该方法实现有所不同,后文会解释。

2.1.1 线程属性

/**
  * 线程名,构造Thread时或通过{@link #setName(String)}设置,
  * 通过{@link #getName()}获取
  */
private volatile String name;
/**
 * 线程优先级,会对应到底层操作系统设置,值越大优先级越高,可能不会生效,一般用下面预置值:
 * public static final int MIN_PRIORITY = 1;
 * public static final int NORM_PRIORITY = 5;
 * public static final int MAX_PRIORITY = 10;
 * 默认继承父线程优先级
 */
private int priority;
/**
 * 是否后台(守护)线程,通过{@link #setDaemon(boolean)}与{@link #isDaemon()}设置和读取
 * setDaemon必须在start之前调用,后台线程随着普通线程结束而结束
 */
private boolean daemon = false;
/**
 * 部分jvm实现会用到的状态,表示线程在start之前是否已经stopped
 */
private boolean stillborn = false;
/**
 * 指向线程本地存储的地址,好像Hotspot VM在用
 */
private long eetop;
/**
 * 线程执行体,通过{@link Thread(Runnable)}传入,如果没有传入,则需要覆盖Thread的run()方法
 */
private Runnable target;
/**
 * 线程组,后面再讲
 */
private ThreadGroup group;
/**
 * 线程上下文类加载器,之前讲过
 */
private ClassLoader contextClassLoader;
/**
 * 继承的访问权限上下文,用于系统资源的访问,可在构造时传入,默认为{@link AccessController#getContext()}
 */
private AccessControlContext inheritedAccessControlContext;
/**
 * 自增的线程号,用于为匿名线程生成线程名:"Thread-" + nextThreadNum()
 */
private static int threadInitNumber;
/**
 * 自增序号,用于得到下一个线程的线程id:tid = nextThreadID()
 */
private static long threadSeqNumber;
/**
 * 线程ID
 */
private final long tid;
/**
 * 当前线程的局部变量map,后文讲
 */
ThreadLocal.ThreadLocalMap threadLocals = null;
/**
 * 继承父线程的局部变量map,后文讲
 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
/**
 * 线程ID,之前讲过线程管理是在用户空间控制的
 */
private final long tid;
/**
 * 栈空间大小,有的虚拟机不支持此参数,一般不需设此值,传0
 */
private final long stackSize;
/**
 * 线程状态,默认为 'not yet started',下文解释
 */
private volatile int threadStatus;
/**
 * java.util.concurrent.locks.LockSupport.park 阻塞线程时设置的同步对象,各线程共享此对象阻塞状态
 * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
 * Accessed using java.util.concurrent.locks.LockSupport.getBlocker
 */
volatile Object parkBlocker;
/**
 * 在可中断IO上阻塞时设置该字段,当线程设置中断状态时,该对象的interrupt()方法被调用
 */
private volatile Interruptible blocker;
/**
 * 用于设置blocker的同步监视器对象
 */
private final Object blockerLock = new Object();
/**
 * 当前线程未捕获异常处理器
 */
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
/**
 * 默认线程未捕获异常处理器,对所有线程,如果没有定义异常处理器,而且其线程组(包括父线程组)也没有
 * 异常处理器,则在遇到未捕获异常时调用该默认异常处理器
 */
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
/**
 * 下面3个为ThreadLocalRandom类相关数据,在讲ThreadLocalRandom时再说
 */
long threadLocalRandomSeed;
int threadLocalRandomProbe;
int threadLocalRandomSecondarySeed;

2.1.2 构造方法

/**
 * 初始化线程,其他构造器最终都是调用此私有构造器,在之前版本中是init方法
 *
 */
private Thread(ThreadGroup g, Runnable target, String name,
               long stackSize, AccessControlContext acc,
               boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    // 此时该线程还未start,所以currentThread()返回的时创建该线程的线程
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        // 如果SecurityManager存在, 通过SecurityManager#getThreadGroup获取.
        // 最终调用Thread.currentThread().getThreadGroup()返回SecurityManager线程线程组
        if (security != null) {
           g = security.getThreadGroup();
        }

        // 如果获取不到,则返回父线程线程组
        if (g == null) {
           g = parent.getThreadGroup();
        }
     }

     /* 检查线程组修改权限. */
     g.checkAccess();

     /*
      * 检查是否有enableContextClassLoaderOverride权限
      */
     if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(
                   SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
     }
		 // 增加ThreadGroup中未开始线程数
     g.addUnstarted();

     this.group = g;
     // 继承父线程daemon属性
     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;
     // 最终调用setPriority0设置本地平台线程优先级
     setPriority(priority);
  	 // 从父线程拷贝值,拷贝后自线程对变量值的修改,不会影响父线程
     if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
     /* Stash the specified stack size in case the VM cares */
     this.stackSize = stackSize;

     /* Set thread ID */
     this.tid = nextThreadID();
}

2.1.2 JNI方法

Thread依赖于底层实现,所以该类中声明了许多native方法,这些方法是在类构造器中进行注册的:

/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
    registerNatives();
}

具体注册的本地方法有,基本看名字就知道用途了,具体后面再讲:

/** 开始线程,会创建一个本地线程 */
private native void start0();
private native void stop0(Object o);
private native void suspend0();
private native void resume0();
private native void setPriority0(int newPriority);
private native void interrupt0();
private native void setNativeName(String name);
public static native Thread currentThread();
public static native void yield();
public static native void sleep(long millis) throws InterruptedException;
private native boolean isInterrupted(boolean ClearInterrupted);
public final native boolean isAlive();
@Deprecated
public native int countStackFrames();
/**
 * 当前线程是否持有制定对象的监视器锁
 * 可用作断言:assert Thread.holdsLock(obj);
 */
public static native boolean holdsLock(Object obj);
/** 获取堆栈快照 */
private native static StackTraceElement[][] dumpThreads(Thread[] threads);
/** 获取当前时间点所有线程 */
private native static Thread[] getThreads();

2.2 线程创建与运行

   首先,虽然Thread实现了Runnable接口,但是却无法实现有意义的线程执行体,实际上它也无法提供,我们看一下Thread类中run()方法的实现:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

	从默认实现可以看到,如果通过Thread构造器传入了Runnable对象,则执行该对象的run方法,否则就是一个空方法,什么也不做。所以,下面代码没有意义:

new Thread().start();

要创建一个线程,要么继承Thread类,重写run方法,要么,提供一个Runnable对象,总的来说,有以下几种方式。

2.2.1 继承Thread

public static void main(String[] args) {
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("hello");
        }
    }
    MyThread myThread = new MyThread();
    myThread.start();
  
  	//匿名类
  	new Thread() {
        @Override
        public void run() {
            System.out.println("hello");
        }
    }.start();
}

// 创建线程时,可传入下列参数
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
    //...
}

2.2.2 实现Runnable

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
};

new Thread(r).start();

2.2.3 Callable 与 FutureTask

public static void main(String[] args) {
    FutureTask task = new FutureTask(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return "hello";
        }
    });

    new Thread(task).start();
    try {
        System.out.println(task.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

Callable有个带返回值的call()方法代表线程执行体,FutureTask实现了Runnable,所以可以作为参数传递给Thread。关于FutureTask以后单独介绍。

2.2.4 用线程池执行

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    executorService.submit(() -> System.out.println("hello"));
}

线程池的方式通过复用线程来降低每次创建线程的性能消耗

关于线程池,后面文章详细讲

2.2.5 运行线程

通过构造器,只是创建了一个普通java对象而已,若此时直接调用thread.run()则不会以线程方式执行,只是一个普通方法调用而已。要运行线程,需要调用start()方法。

public synchronized void start() {
        // 只有NEW状态下,才能调用,不能对一个thread多次调用start
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        // 将线程添加至所属线程组的线程列表,并将未启动线程数减一
        group.add(this);

        boolean started = false;
        try {
          	// 调用VM方法创建一个系统线程并运行
            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 */
            }
        }
    }

2.3 线程生命周期

2.3.1 线程状态

线程共有以下几种状态(threadStatus):

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
		WAITING,
  	TIMED_WAITING,
  	TERMINATED;
    }
  • New 新建,Thread刚创建,还没有start。此时只是创建了一个java的Thread对象,底层线程还未创建。
  • RUNNABLE 就绪(可执行),等待处理器调度。在调用了Thread#start()方法后,或
  • BLOCKED 阻塞,在同步块/同步方法的监视器锁上等待,以进入(或在调用了{@link Object#wait()}方法后重入)同步块/同步方法
  • WAITING 无限期等待,等待另一个线程唤醒或运行结束。当调用以下方法后进入WAITING状态:
    • Object#wait(),等待另一个线程调用notify()/notifyAll()
    • AnnotherThread#join(),等待被join的线程执行完毕
    • LockSupport#park(),等待另一个线程调用LockSupport#unpark(thread)
  • TIMED_WAITING 有限期等待,等待另一个线程唤醒或运行结束,或在制定时间后。当调用以下方法后进入TIMED_WAITING状态:
    • Thread.sleep
    • 带timeout的Object.wait
    • 带timeout的Thread.join
    • LockSupport#parkNanos
    • LockSupport#parkUntil
  • TERMINATED 执行完成或异常退出

2.3.2 线程状态转换图

注:此图只描述java Thread中定义的状态转换

2.4 控制线程

这一节,介绍一些控制线程执行的方法

2.4.1 join线程

join使当前线程等待另一个线程执行完成。

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

join实现如下:

/**
 * 等待被join线程执行完成,millis为0时,则无限期等待
 */
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;
            }
        }
    }

可以看到,方法实现采用了循环+wait等待线程die。当一个线程状态变为TERMINATED后会调用notifyAll,保证了该实现的可行性。

在用户代码中,不要在Thread对象上使用wait/notify/notifyAll

2.4.2 后台线程

后台线程,也称守护线程/精灵线程,当所有用户线程执行完毕后,后台线程也随之退出了。

public static void main(String[] args) {
    Thread daemonThread = new Thread(() -> {
        while (true) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " run.");
        }
    }, "后台线程");
    daemonThread.setDaemon(true);
    daemonThread.start();

    Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("进程退出")));

  	// 启动一个用户线程
    new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(5);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println("用户线程执行完成");
    }).start();
}
  • daemon默认为false,setDaemon(true)必须在start之前调用
  • daemon属性会从父线程继承,所以,daemon线程中新建的线程也是daemon线程
  • 默认的线程池实现会将daemon属性设为false
  • 当需要在后台完成一些工作时就可以使用后台线程,如jvm垃圾回收线程

2.4.3 Sleep

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

sleep方法用来暂停当前线程的执行指定时间(TIMED_WAITING),让出CPU时间片,但并不释放所持有的monitors,在指定时间后进入RUNNABLE状态,底层基于定时器实现。

暂停状态是可中断的,当暂停线程被中断时,抛出InterruptedException

2.4.4 yield

public static native void yield();

yield方法使当前线程让出CPU时间片,自己则变为RUNNABLE状态重新进入调度队列。处理器可能忽略这个调用,该方法同样不会释放锁。

2.4.5 线程优先级

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
  	// 优先级大小[1, 10]
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
          	// 不能超过线程池最大优先级限制
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

优先级越高,获得调度的机会就越大,具体优先级,取决不同的平台实现,当调度的线程不是很多,处理器处理速度有很快时,不会有明显的效果,默认为NORM_PRIORITY=5

2.4.6 未捕获异常处理

为线程线程设置未捕获异常处理器,优先使用当前线程设置的处理器,否则使用所属线程组及其父线程组中定义的处理器,最后使用默认异常处理器。

public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
            System.out.println("[DefaultUncaughtExceptionHandler]Thread " + t.getName() + " exception happened");
            e.printStackTrace();
        });

        Thread thread = new Thread(() -> System.out.println(1 / 0), "ExThread");

//        thread.setUncaughtExceptionHandler((t, e) -> {
//            System.out.println("[UncaughtExceptionHandler]Thread " + t.getName() + " exception happened");
//            e.printStackTrace();
//        });

        thread.start();
    }

开始,DefaultUncaughtExceptionHandler会触发,去掉注释后改为使用UncaughtExceptionHandler

ThreadGroup,提供了uncaughtException方法,当前线程没有设置异常处理器时,在使用默认异常处理之前,jvm会先调用次方法

public void uncaughtException(Thread t, Throwable e) {
  			// 尝试抛给父线程组处理
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
          	// 定义了默认处理器,则使用默认处理器处理
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
              	// 否则直接打印异常堆栈
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

在线程中,还是推荐自己捕获异常,另外注意的一点是关于中断异常的处理,以后再介绍

关于Thread基本内容介绍完了,后面在写一些关于对象锁,线程通信,线程同步相关知识。