2 创建线程对象
线程是一个子任务,那么如何创建这个子任务的对象呢?
方法一: 通过继承线程类 Thread 来创建线程对象; 另一个方法是通过实现 Runnable 接口来创建线程对象。
让我们来逐一了解这两个方法的实现过程。
2. 1 通过继承 Thread 类创建线程对象
来段代码当开胃菜:
package Xg27;
class TestThread extends Thread{
public TestThread(String str) {super(str);}
public void run() {
for (int i = 0; i < 2; i ++) {
System.out.println(getName() + "在运行阶段");
try { sleep(1000);
System.out.println(getName()+"在休眠阶段");
} catch(InterruptedException e) {}
System.out.println(getName() + "已结束");
}
}
}
public class exp9_1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
TestThread t1 = new TestThread("线程1");
TestThread t2 = new TestThread("线程2");
t1.start(); t2.start();
}
}
运行结果:
线程2在运行阶段
线程1在运行阶段
线程1在休眠阶段
线程1已结束
线程1在运行阶段
线程2在休眠阶段
线程2已结束
线程2在运行阶段
线程1在休眠阶段
线程1已结束
线程2在休眠阶段
线程2已结束
在程序中通过继承 Thread 类创建了一个内部线程子类 TestThread ,在 exp9_1 主类中创建两个线程对象 t1 和 t2,它们的任务是输出线程的状态。
从上面的运行结果我们可以看出:
(1)线程的名字是交替显示的,这是因为这两个线程是同步的。所以两个 run方法也同时被执行。
(2)每一个线程运行到输出语句时将在屏幕上显示自己的名字,执行到 sleep 语句时将休眠 1000毫秒, 线程休眠时不会占用CPU,其他线程可以继续运行。 一旦延迟完毕,线程将被唤醒,继续执行语句。
(3)线程的执行顺序是由操作系统调度和控制的,因此,每次运行程序其中线程的顺序是不同的。
- 继承 Thread 的子类必须覆盖 Thread 类的 run 方法(尤其是空方法)。run 是线程类的关键方法,线程所有的都是通过它来实现的(可以理解,线程从 start 之后就是要一直跑的嘛),当调用线程对象时通过start 方法自动调用 Theard 类 run方法,通过 run 方法实现线程的目的, run 方法的作用如同 Application 应用程序的 main 方法一样。 就像 100 m 跑一样,听到枪响(start),只需要一直跑到终点就对了。
2.2 通过 Runnable 接口创建线程对象
接口 Runnable 中只声明一个空的 run 方法, 专门用来创建线程对象的接口,特别的是当一个类是从其他类继承时, 用 Runnable 创建线程对象比继承 Thread 更适合。
实例:通过实现接口 Runnable 创建线程对象的应用程序
class TestRunnable implements Runnable {
public void run() {
int i =15;
while(i -->= 1) {
try {System.out.print(i+ "*"); Thread.sleep(500);}
catch (Exception e) {e.printStackTrace();}
}
}
}
public class exp9_1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
TestRunnable tr = new TestRunnable();
Thread t1 = new Thread(tr,"线程1");
t1.start();
}
}
运行结果:
14*13*12*11*10*9*8*7*6*5*4*3*2*1*0*
本例创建了实现接口 Runnable 的进程类 TestRunnable 类,重写了接口的 run()方法,在主方法中,用进程类 TestRunnable 的对象 tr ,实例化 Thread 对象, 由其调用 start() 开始进程。
两种创建线程对象方法的比较:
(1)由继承 Thread 类创建线程对象简单方便, 可以直接操作线程,但不能再继承其他类。
(2)用 Runnable 接口创建线程对象,需要实例化为 Thread 对象, 可以再继承其他类。
拓展:
文章来源:https://www.jianshu.com/p/a8abe097d4ed
异常类型之 ——InterruptedException 。
在第一种线程对象创建的方法中,我们看到这样一种异常对象,那什么时候会抛出这种异常呢?
首先来了解一下关于线程的一些基础知识。
线程在一定的条件下回发生状态的改变。下面是线程的一些状态。
- 初始 (NEW):新建一个线程的对象,还未调用start方法
- 运行 (RUNNABLE):java线程中将已经准备就绪(Ready)和正在运行中(Running)的两种状态都统称为“Runnable”。准备就绪的线程会被放在线程池中等待被调用。
- 阻塞 (BLOCKED):是因为某种的原因(下面会讲)而放弃了CPU的使用权,暂时的停止了运行。直到线程进入准备就绪(Ready)状态才会有机会转到运行状态。(其实还是蛮好理解的吧)
- 等待 (WAITING):该状态的线程需要等待其他线程做出一些特定的动作(通知或者是中断)。
- 超时等待 (TIME_WAITING):该状态和上面的等待不同,他可以在指定的时间内自行返回
- 终止 (TERMINATED):线程任务执行完毕。
线程阻塞
线程阻塞通常是指一个线程在执行过程中暂停,以等待某个条件的触发,而什么情况才会使得线程进入阻塞的状态呢?
- 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、。join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
线程中断
如果我们有一个运行中的软件,例如是杀毒软件正在全盘查杀病毒, 如果我们电机取消,那么就是正在中断一个运行的线程。那么如何执行这个中断的请求呢?
每一个线程都有一个 boolean 类型的标志,这个标志的意思是判断自己是否被中断, 默认为 false。
线程的独白:每天辛苦工作,随时都可能被主人中断,还要时时刻刻看自己是不是被中断了, 这种觉悟我哭了,你们呢。
当线程 A 调用了线程 B 的 interrupt 方法时,线程 B 的是否请求的中断标志变成了 true 。而线程 B 可以调用方法检测到此标志的变化。
有两种方法可以检测“是否请求中断标志”是否发生变化:
- 阻塞方法: 如果线程 B 调用了阻塞方法, 如果“是否请求中断标志变为了 true”,那么它会抛出 InterruptedException 异常。 抛出异常的同时它会将线程 B的是否请求中断标志置为 false 。
- 非阻塞方法: 可以通过 B 的 isInterrupted 方法进行检测是否请求中断标志为 true 还是 false, 另外还有一个静态的方法 interrupted() 也可以检测标志,并且自动将是否请求中断标志设为 false 。
线程B 感受到别人向它发出中断请求(委婉的表示:兄弟你该下机了),但是感受到是一回事,是否中断还是取决于线程自己。
之后的处理其实很重要,当线程B 有中断的时候它的方法会自动把中断的状态改为 true ,这就意味着你给中断打了个招呼,说:哥们,你该停了, 线程的助手提醒了它,顺便帮它把这个中断改成了 true ,就像早上定了闹钟,脑袋还没清醒,手就不由自主的关了闹钟(有时候真的很佩服自己,为什么能想出这么天才的比喻),关键是我现在要醒啊,所以文章后来写到,要在抛出异常 InterruptedException之后把中断再给它改为 false ,最起码要让高层知道,这个线程是要中断的,就像闹钟设置了 10 分钟后重新响铃一样,避免因为自动更改状态值而影响正常运行。