前言
一 结构关系
public
class
Thread
implements
Runnable
{
...... }
很显然
Thread继承了Runnable。
Runnable源码如下:
public interface Runnable { public abstract void run(); }
Runnable很简单,它是一个接口,只用一个方法run();
二 构造函数
Thread#构造方法:
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } public Thread(ThreadGroup group, Runnable target) { init(group, target, "Thread-" + nextThreadNum(), 0); } ......
说明,这只是部分构造方法,从这些构造方法中可以看出都是调用了函数
init。
Thread#
init
:
/** * 很明显这个方法是用来初始化线程的 * @param g 线程组 你创建的线程最终会被添加到这个线程组中 * @param target Runnable类型 用于回调 * @param name 即将创建的线程的名字 * @param stackSize 即将创建的线程的栈的大小,如果是0,表明忽略此参数 */ private void init(ThreadGroup g, Runnable target, String name, long stackSize) { //调用native层的currentThread函数获取当前环境所在的线程,例如在Activity中,你获取的将是UI线程 Thread parent = currentThread(); if (g == null) { //线程parent所属的线程组 g = parent.getThreadGroup(); } //函数1 后台线程数加1 g.addUnstarted(); //保存线程组 this.group = g; //保存 Runnable类型 用于回调 this.target = target; //parent获取线程优先权1-10 this.priority = parent.getPriority(); //是否是后台线程 this.daemon = parent.isDaemon(); //设置线程的名字 setName(name); //函数2 init2(parent); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; //创建线程Id,线程的唯一标识 tid = nextThreadID(); }
1 addUnstarted
Thread#
addUnstarted
:
void addUnstarted() { synchronized(this) { if (destroyed) { throw new IllegalThreadStateException(); } nUnstartedThreads++; } }
注释已经说的很清楚了:
/**
* Increments the count of unstarted threads in the thread group.
* Unstarted threads are not added to the thread group so that they
* can be collected if they are never started, but they must be
* counted so that daemon thread groups with unstarted threads in
* them are not destroyed.
*/
大致意思就是:把
thread group
中的即将启动的线程数加一,这样可以在该线程没有启动的情况,避免被销毁。
2 int2
Thread#
int2
:
private void init2(Thread parent) { //获取线程parent的类加载器 ClassLoader类型 this.contextClassLoader = parent.getContextClassLoader(); this.inheritedAccessControlContext = AccessController.getContext(); if (parent.inheritableThreadLocals != null) { //给当前线程创建ThreadLocalMap对象,并且继承parent的ThreadLocalMap中的数据 this.inheritableThreadLocals = ThreadLocal.createInheritedMap( parent.inheritableThreadLocals); } }
说明,这里有两点需要注意下:
a
:
类加载器即ClassLoader,那么什么是类加载器?一个完整的Java程序可能由几个.class文件组成,程序在运行的过程中并不是一下子把所有的.class文件放置内存中,而是通过类加载器把需要的.class文件加载到内存中,进而其他的.class文件可访问到这个该.class文件。想了解更多关于ClassLoader,可以参考这篇《深入分析Java 类加载器》文章。
b:ThreadLocalMap 它是ThreadLocal的内部类,每个Thread都一个
ThreadLocalMap,
其结构是Map类型,key存储的是当前线程的ThreadLocal对象,value存储的是Object类型的变量,其作用使用
ThreadLocal声明一个变量时,
ThreadLocal会为每个线程创建这个变量的副本,当线程对这个变量进行操作时,互相之间不受影响,在一定程度上解决多线程不安全的问题。关于
ThreadLocal以后会详细介绍。
三 生命周期
1线程状态
public enum State { NEW,//被实例化之后,但还没有调用start启动 RUNNABLE,//调用了start函数之后就处于Runnable状态 BLOCKED,//调用join()、sleep()、wait()使线程处于Blocked状态 WAITING,// TIMED_WAITING,// TERMINATED;// }
(1) new
new也称为新线程状态,通过new关键字实例化一个Thread对象就生成一个新线程。当线程处于"新线程"状态时,仅仅是一个空线程对象,它还没有分配到系统资源。因此只能启动或终止它。任何其他操作都会引发异常。例如,一个线程调用了new方法之后,并在调用start方法之前的处于新线程状态,可以调用start和stop方法。
(2)Runnable
Runnable也称为
可运行状态
,通过start方法后使线程处于该状态,此时线程获取了支持其运行的资源,并调度其run方法,这个
状态
不能想当然的认为是运行状态,
因为这时的线程并不总是一直占用处理机,
它也有可能不在
运行
,这是因为还有优先级和调度问题
。
特别是对于只有一个处理机的PC而言,任何时刻只能有一个处于可
运行
状
态的线程占用处理机。Java通过调度来实现多线程
对
处理机
的共享。
(3)NOT
Runnable
NOT
Runnable也称为
阻塞
状态,
当以下事件发生时,线程处于该状态:
a:调用supped、sleep方法
b:调用wait方法等待条件变量
c:线程处于I/O请求的等待
(4)Dead
Dead
也称为
死亡状态
,run方法运行完毕、其他线程调用该线程的stop方法、异常终止都会使线程处理该状态。
2 线程的操作:
好,接下来借用网友一幅图加于说明:派生:线程在进程内派生出来,它即可由进程派生,也可由线程派生。阻塞(Block):如果一个线程在执行过程中需要等待某个事件发生,则被阻塞。激活(unblock):如果 阻塞 线程的事件发生,则该线程被激活并进入就绪 队列。调度(schedule):选择一个 就绪 线程进入执行 状态 。
四 函数分析
1 start函数分析
一个线程启动调用的是start函数,下面我们看下它的源码:
Thread#
start
:
public synchronized void start() { /** *threadStatus ==0,说明该线程状态为"NEW",还没有启动过,一个线程只能启动一次,否则抛出异常。 */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* 把当前线程添加到线程组中 */ group.add(this); started = false; try { //调用native层函数创建线程,native层的源码未找到先不讨论它 nativeCreate(this, stackSize, daemon); started = true; } finally { ...... } }
说明,这个方法主要两个作用:
a:把线程添加到线程组中
b:调用native层的nativeCreate函数完成线程的创建。
2 interrupt函数分析
Thread#
interrupt
:
public void interrupt() { if (this != Thread.currentThread()) checkAccess(); //blockerLock synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { nativeInterrupt(); b.interrupt(this); return; } }//调用native层函数完成线程中断 nativeInterrupt(); }
说明,在Java中“中断”线程是通过interrupt()方法来实现的,之所以加引号,是因为interrupt()并不中断正在运行的线程,只是向线程发送一个中断请求,具体行为依赖于线程的状态,如下:
a:
如果线程处于阻塞状态,即线程被Object.wait()、Thread.join()或 Thread.sleep()阻塞,调用interrupt()方法,将接收到InterruptedException异常,中断状态被清除,结束阻塞状态;
b:
如果线程在进行I/O操作(java.nio.channels.InterruptibleChannel)时被阻塞,那么线程将收到java.nio.channels.ClosedByInterruptException异常,通道被关闭,结束阻塞状态;
c:
如果线程被阻塞在java.nio.channels.Selector中,那么中断状态会被置位并返回,不会抛出异常。
3 join函数分析
Thread#
join
:
public final void join() throws InterruptedException { join(0); } public final void join(long millis) throws InterruptedException { synchronized(lock) { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { lock.wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } lock.wait(delay); now = System.currentTimeMillis() - base; } } } }
说明,join()方法也可以理解为线程之间协作的一种方式,当两个线程需要顺序执行时,调用第一个线程的join()方法能使该线程阻塞,其依然通过wait()方法来实现的。
4 sleep与wait函数分析
Thread#
sleep
:
public static void sleep(long millis) throws InterruptedException { Thread.sleep(millis, 0); }
Thread#
wait
:
public final void wait(long millis) throws InterruptedException { wait(millis, 0); }
说明,sleep()与wait()的相同之处在于它们都是通过等待阻塞线程,不同之处在于sleep()等待的是时间,wait()等待的是对象的锁,在这里是看不出来的。
5 常用函数总结
run():包含线程运行时所执行的代码
start():用于启动线程
sleep()、sleep(long millis):线程休眠,交出CPU,让CPU去执行其他的任务,然后线程进入阻塞状态,sleep方法不会释放锁
yield():使当前线程交出CPU,让CPU去执行其他的任务,但不会是线程进入阻塞状态,而是重置为就绪状态,yield方法不会释放锁
join()、join(long millis)、join(long millis,int nanoseconds):等待线程终止,直白的说 就是发起该子线程的线程 只有等待该子线程运行结束才能继续往下运行
wait():交出cpu,让CPU去执行其他的任务,让线程进入阻塞状态,同时也会释放锁
interrupt():中断线程,自stop函数过时之后,我们通过interrupt方法和isInterrupted()方法来停止正在运行的线程,注意只能中断已经处于阻塞的线程
getId():获取当前线程的ID
getName()、setName():获取和设置线程的名字
getPriority()、setPriority():获取和这是线程的优先级 一般property用1-10的整数表示,默认优先级是5,优先级最高是10,优先级高的线程被执行的机率高
setDaemon()、isDaemo():设置和判断是否是守护线程
currentThread():静态函数获取当前线程
参考文献