我们上学的时候都知道线程有两种方式,要么继承Thread类,要么实现runable接口。根据我们上次对线程池的分析,发现我们对Thread类的理解还比较浅显。所以深入理解Thread成为我们以后学习线程池的基础。
代码语言:javascript复制实现Runnable接口方式
public class ShowMessage implements Runnable{
private String message="任务";
@Override
public void run() {
System.out.println(message);
}
public static void main(String[] args) {
Thread thread=new Thread(new ShowMessage());
thread.start();
}
}
继承方式
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("继承的任务");
}
public static void main(String[] args) {
new MyThread().start();
}
}
通过上述的例子我们大致的信息是Runnable的run是线程执行的核心。那么到底是这样吗?我们先看一下Thread类和Runnable接口的关系,在没有查看源代码的前提下我个人觉得Thread肯定是实现了Runnable接口的。
而通过查看Runnable接口定义的方法,我们发现其实Runnable接口只有一个方法那就是run方法。
那么我们是不是可以得出的结论:thread类相当于一个工具类,专门用来将执行runnable的任务,当然由于Thread自身实现了Runnable接口所以它本身内部也可以直接定义任务。那么问题就变成了Thread如何开辟线程并进行运行的了,但是相对于高级语言来说它肯定是调用的操作系统提供的api了,所以说我们前进的步法可能就止于逻辑的合理性上了。
在上边的Thread类的结构图上,我们看到类的基本结构。我们回顾一下我们上一文中的ThreadPoolExecutor的任务队列,通过我们的分析,难么任务队列也必然就是一系列实现了Runnable接口的任务,而Thread类就全盘交付给了ThreadFactory了。其实分析到这里,我们基本上都不用看Thread类就可以去尝试看ThreadPoolExecutor的实现了。可是既然我们来了那就多多少少看看Thread类吧,毕竟Thread类维护线程的状态还是比较麻烦的。
根据类的实例化过程我们首先注意到的是registerNatives方法,这是native方法显然这块应该是将这个线程注册到哪里?根据一般规则,肯定是操作系统了。目前来说就这样理解吧。
代码语言:javascript复制 private static native void registerNatives();
static {
registerNatives();
}
我们看到所有的初始化方法最终都调用的init方法
代码语言:javascript复制 private void init(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;
//当前线程
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()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();
//设置真正的任务
this.target = target;
//设置线程的优先级
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//设置线程的栈空间大小
this.stackSize = stackSize;
//设置线程id
tid = nextThreadID();
}
但是其中最重要的方法是native,所以parent就是已经创建好的线程了。
代码语言:javascript复制public static native Thread currentThread();
看到上述代码已经将线程所需要的限制条件进行设置,那么下面的方法就是在条件范围内玩逻辑了
那么我们看看start()方法内在的实现逻辑。
代码语言:javascript复制public synchronized void start() {
//如果线程的状态不是新建的就抛出异常,因为start肯定是新的线程状态也就是0了
if (threadStatus != 0)
throw new IllegalThreadStateException();
//上边在初始化的过程中,我们发现只是进行线程组的赋值,而不是添加,真正添加到线程组的操作是在启动线程的时候进行的
group.add(this);
boolean started = false;
try {
//调用的native方法启动线程
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 */
}
}
}
//剔除启动失败的线程
void threadStartFailed(Thread t) {
synchronized(this) {
remove(t);
nUnstartedThreads ;
}
}
但是启动线程的时候我们并没有发现那个方法调用了run方法,显然是start0在native层面上掉用的run方法。而run方法的本质是runnable接口。所以任务和线程是分开的。也验证了我们上述的说法。
代码语言:javascript复制 private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
线程中断的时候是通过加锁的方式进行的,防止多个并发线程去竞争中断其他线程,interrupt0也是调用的native方法。
代码语言:javascript复制 public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
代码语言:javascript复制获取存活线程的数量
public static int activeCount() {
return currentThread().getThreadGroup().activeCount();
}
//在join方法中,采用wait阻塞的方式让线程在指定时间内休眠
代码语言:javascript复制 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;
}
}
}
在线程优先级上
代码语言:javascript复制 //最小优先级
public final static int MIN_PRIORITY = 1;
//默认的优先级
public final static int NORM_PRIORITY = 5;
//最大的优先级
public final static int MAX_PRIORITY = 10;
线程的五种状态
新建:当我们new Thread()就相当于创建了一个线程,这个线程向下进行注册,那么这个线程就处于在编模式,但并没有启动。那么这个这样的状态叫做新建。
就绪:当线程调用start0方法并执行玩start方法返回成功,但并没有执行run方法中的代码片段。而且线程有很多,但是cpu的核数是有限的。所以没有执行run方法的线程称之为就绪。 通过我们在start方法的学习,如果start成功了那么线程组就将这个线程添加到线程组,否则就剔除掉。那么是否可以说明在线程组中注册的线程都曾经历过就绪态?
运行:当线程获得cpu时间片段并执行run方法的时候称之为运行态。
阻塞:正在运行run方法的线程由于某种原因让出cpu让其他就绪态的线程使用cpu资源。
死亡:run方法执行结束或者run方法抛出了异常没有被处理,并跳出了run方法导致的线程猝死。另外isAlive方法可以用来判断线程是否是活着的,在就绪、阻塞和运行态时候会返回true,其他状态返回false。