创建线程的第一种方式:继承Thread类
- 定义一个类,继承Thread类
- 重写Thread类中的run方法
- 创建Thread类的实现类的对象
- 利用该对象调用Thread类中的start方法
运行结果
好了,接下来我们看源码。
首先我们看一下Thread类。
可以看到Thread类实现了Runnable接口,这是另外一种创建线程的方式,我们会在之后的章节中给出。
在类声明的下面是静态代码块,也就是static{}包裹的部分,里面的代码会在类加载的时候执行一次,之后便不会在执行。
我们可以来尝试一下:
第一种,主类和静态代码块分开的情况:
运行结果是:
第二种:静态代码块所在的类也是主程序类,即:
运行结果和上次的不一样了:
发现了吗,静态代码块在还没有创建实例的时候就执行了,所以静态代码块是在类加载的时候创建的,而不是初始化。
好了,课外补充时间结束,我们回到Thread正题中来。
Thread中的静态代码块表示注册该Thread类,只会注册一次,这是防止Thread类未被注册的,也就是程序要确保注册是要做的第一件事。
Thread类中的变量和方法有:
Eclipse中查找一个类中所有的变量和方法的快捷键:Ctrl+O
Private volatile String name
这表示是线程的名字
我们可以在创建线程的时候修改名字:
如果我们没有指定名字,他会有默认的名字:
我们先来看看我们平时用无参Thread创建Thread对象的过程:
我们再来看看Thread类中的start方法
以上继承Thread类重写run方法创建线程并执行的过程。
可以发现,Thread类中很多都调用了底层的本地方法。
之后再来看看Thread类中有什么方法:
有两个init方法,都是private类型的,也就是说,除非是Thread类中的方法调用这两个方法,否则其他外部的类是无法调用该方法的。(不过应该可以用反射的方式调用,只要设置了权限,或者说暴力反射,也就是setAccessible)
Init方法是创建Thread对象的核心方法,我们使用的new Thread最终都调用的该方法进行创建。
该类重写了Object类的clone方法(也就是复制方法)(Java中所有的类隐式的继承Object类),他不允许克隆线程,用户调用该方法会返回一个不能克隆的异常。
Thread类中有很多个构造方法,如下:
Public static native Threa currentThread():返回当前正在执行的线程
Public static native void yield():提示系统当前线程愿意让步,也就是从执行状态回到就绪状态,但是注意,它有可能又马上就执行了.
Public static native void sleep(long):让当前线程休眠即毫秒
Public static void sleep(long,int):让当前线程休眠几毫秒加及纳秒
Protected Object clone():克隆,重写Object类的方法,禁止克隆,克隆方法一调用就抛不能克隆的异常(CloneNotSupportException)
Public synchronized void start():开启线程,如果启动失败会重新启动
Private native void start0(),启动线程实际调用的方法,是本地方法
Public void run():我们重写的Run方法,注意,如果我们是用继承Thread类的方法实现的话,他就是我们重写的方法,如果我们传入了Runnable实现类对象,他就是实现类对象中的run方法。
Private void exit():这个方法把一切包括线程组全部置为NULL
Public final void stop():终止线程的方法,已过时,因为不安全,他不管线程目前什么状态,执行了什么,执行完了没有,直接就把人扔出去了,所以不安全
Public void interrupt():这个方法说是终止线程,但是发现无论是使用在Run方法还是Main方法中,都不能阻止线程执行,但是如果线程陷入睡眠状态,比如Sleep的话,使用该方法会抛出InterruptException异常
比如:
package com.lqy.JavaBasics.Multithreading.Creation;
//测试Thread类中的interrupt能不能终端线程,然而无论是放在Run方法中还是Main方法中都不能
/*
* interrupt方法用于打断sleep、wait、join的线程,interrupt不会真的中断线程,他的本质只是设置一个中断状态,让该被打断的线程决定如何结束
* 当线程处于sleep、wait、join等阻塞状态时,interrupt方法会清空打断状态并抛出InterruptException异常
* 我们需要注意的是,Java中凡是抛出InterruptException的方法(再加上Thread.interrupted)都会在抛出异常的时候,将interrupt Flag重新置为False
* 这也就是说,当一个线程B被中断(被使用interrupt方法)的时候正处于睡眠状态(比如Sleep),他就会结束Sleep状态并且抛出InterruptException异常,并且,将中断位标记位False
* 此时我们再去检查interrupt Flag的状态,他是False,不能正面他被中断了,现在唯一能证明当前线程B被中断的证据就是抛出的InterruptException异常,如果我们没有捕获处理直接扔掉的话
* 恭喜你,你绝对不知道你的程序都经历了什么
* 简而言之,interrupt方法只是将线程的中断位标记为True,表示是要中断的,但是中不中断,不管
* 但是你可以使用了这个方法后检查中断位,检查到中断位为True的,停止线程,至于那些睡眠状态的,我们就捕获InterruptException异常,在Catch块中终止
* 至于具体的怎么用它来中断线程,我们在TestInterrupted类中解释
*/
public class TestInterrupt extends Thread{
int i=0;
public void run() {
try {
//单位为毫秒。一秒等于1000毫秒
Thread.sleep(1000000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// for(int i=0;i<1000;i++) {
// System.out.println(i);
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
if(i==10) {
this.interrupt();
}
// }
}
public static void main(String[] args) {
TestInterrupt testInterrupt=new TestInterrupt();
testInterrupt.start();
testInterrupt.interrupt();
}
}
运行结果:
public class TestInterrupted extends Thread{
public void run() {
for(int i=0;;i++) {
System.out.println(i);
// this.interrupt();
//注意:interrupted使用后会清空当前判断位,也就是说无论之前是True还是False,使用过一次后就变回False
//但是isInterrupted不会清空当前状态
//比如下面两句输出中,interrupted使用输出后,if语句就永远不会为True
// System.out.println(this.interrupted());
// System.out.println(this.isInterrupted());
// if(this.isInterrupted()) {
// System.out.println("线程终止了");
// break;
// }
if(this.interrupted()) {
System.out.println("线程终止了");
break;
}
}
}
public static void main(String[] args) {
TestInterrupted testInterrupted=new TestInterrupted();
testInterrupted.start();
testInterrupted.interrupt();
}
}
这样,方法中就能中断线程了
其实主要就是设置一个状态位,状态位为True就停止执行,因为线程无法结束就是因为重写的Run方法里面有死循环,如果没有的话,在运行完毕后,线程会自动停止,就和方法执行完毕一样,所以我们需要终止线程,其实就是退出循环。
我这里说这一点的原因是希望用的时候明白,使用这两种方式之后,不是直接就退出了Run方法,如果你只是退出了循环,循环之后的Run方法内的代码依然会执行,比如:
我将Run方法增加一条
public void run() {
for(int i=0;;i++) {
System.out.println(i);
// this.interrupt();
//注意:interrupted使用后会清空当前判断位,也就是说无论之前是True还是False,使用过一次后就变回False
//但是isInterrupted不会清空当前状态
//比如下面两句输出中,interrupted使用输出后,if语句就永远不会为True
// System.out.println(this.interrupted());
// System.out.println(this.isInterrupted());
// if(this.isInterrupted()) {
// System.out.println("线程终止了");
// break;
// }
if(this.interrupted()) {
System.out.println("线程终止了");
break;
}
}
System.out.println("我是循环后面的,Run方法内的代码");
}
执行结果
为什么我一直强调这一点?因为如果你跳出当前死循环后下面还有一个死循环的情况下,你要考虑清楚你是要退出第一个死循环还是退出Run方法,之后将If语句块中的内容做相应的更改。
Public static Boolean interrupted():获取当前线程的执行状态位,注意,获取之后会重置状态位。
Public Boolean isInterrupted():获取当前线程的执行状态位,获取之后不会重置状态位。
Private native Boolean isInterrupted(Boolean):获取某个线程的中断状态位,上面两种方法实际都是调用这个
Public void destroy():销毁线程的方法,已过时,不建议使用,容易产生死锁
Pubic final native Boolean isAlive():测试此线程是否处于活跃状态
Public final void suspend():如果线程处于活跃状态,则挂起线程,已过时,会产生死锁,不推荐使用
Public final void resume():恢复被挂起的线程,已过时,容易产生死锁,不推荐使用
Public final void setPriority(int):设置线程优先级,1-10,最低为1,最高为10,注意,不是优先级最高就一定优先执行,只是概率更大了
Public final int getPriority():获取当前线程的优先级,默认我们创建线程的优先级为5
Public final synchronized void setName(String):设置线程名字
Public final String getName():获取线程名字
Public final ThreadGroup getThreadGroup():获取当前线程所属的线程组
Public static int activeCount():获取活动线程的数量
Public static int enumerate(Thread[]):将当前活动的线程全部拷贝到指定数组中
Public native int countStackFrames():统计当前线程的堆栈帧数,线程必须暂停,已过时
Public final synchronized void join(long):等待多少毫秒线程才能运行(1秒等于1000毫秒)
Public final synchronized void join(long,int):等待多少毫秒,再加上多少纳秒(1秒等于1000000000纳秒)
Public final vois join():阻塞其他活跃线程,直到该线程执行完毕。这里没有添加sychronized关键字是因为他直接调用了join(long)方法,参数传过去为0
Public static void dumpStack():将当前线程的堆栈跟踪打印到标准错误流,此方法仅用于调试。
比如:
Public final void setDaemon(boolean):将此线程标记为守护线程
Public final Boolean isDaemon():检测此线程是否是守护线程
Public final void checkAccess():看看当前线程有没有修改此线程的权限
Public String toString():重写了ToString方法(Object类中的),使其直接输出的话可以展示线程名字,线程等级和所属线程组名字。
Public ClassLoader getContextClassLoader():获取上下文类加载器,注意:该方法前面有一个@CallerSensitive注解,因为Reflection.getCallerClass()要求调用者必须有@CallerSensitive注解并且拥有权限,比如Class.forName()方法上就有这个注解。
该注解解释来源于:Java注解之@CallerSensitive - 风停了,雨来了 - 博客园 (cnblogs.com)
Public void setContextClassLoader(ClassLoader):自己设置类加载器
Public static native Boolean holdsLocak(Object):当且仅当当前线程在指定的对象(也就是那个Object参数)上保持监视器锁的时候返回True。说人话就是判断线程有没有持有那个锁。
Public StackTraceElement[] getStackTrace():返回当前线程的堆栈跟踪
Public static Map<Thread,StackTraceElement[]> getAllStackTraces():返回所有活动线程的堆栈跟踪信息。
好了,差不多就是这些了,最后,我们来说一下我们常用的四个休眠线程的方法:
Sleep(),wait(),yield()和join()
首先明白,wait方法是Object类的,其他三个是Thread类的。
这四个方法Sleep ,wait ,yield方法是本地方法,注意join不是本地方法,它是调用了wait方法。
Sleep有两个重载方法,分别是sleep(long)和sleep(lon,int)
Join和wait有三个,分别是join(),join(long),join(long,int)和wait(),wait(long),wait(long,int)
虽然这三个方法都是有不止一个重载方法,但是最终其实都调用了只有Long类型参数的方法。
首先,我们来说一下yield()方法
Yield()方法没有参数,它表示的是告知系统当前线程愿意让步,也就是说,当前线程获取了CPU资源后又释放了CPU资源,但是需要注意的是,当前线程没有阻塞,它直接回到就绪队列中等待CPU资源,这也就意味着,下一次获取CPU资源有可能还是这个线程。而且注意,这个方法它不会释放锁,也就是说,假如有一个Synchronized锁,而当前线程在得到了这个锁后放弃了CPU资源,锁不会释放,即便下一个线程得到CPU,如果没有那个锁,也依然只是阻塞在那里而已。
Sleep()方法
Sleep是指让当前线程睡眠几毫秒或者几毫秒+几纳秒。注意,和yeild不一样,它是进入阻塞状态,阻塞了固定时间后才进入就绪状态,注意,它也不会释放锁。
Sleep(0)是休眠0秒的意思。
Wait()方法
Wait方法是Object类的方法,意思也是让线程休眠固定的时间,但是和sleep方法不同的是,它会释放锁。Wait(0)的意思是一直阻塞,但是我们可以用notify或者notifyAll方法唤醒。
Join()方法
Join实际上是调用了wait方法,这也就意味着它也会释放锁,但是和wait不一样的是join的无参构造方法,wait的无参构造相当于wait(0),但是join的无参构造是将所有在活动状态中的线程陷入阻塞,直到调用join()方法的线程全部执行完毕。