多线程机制 (二)

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 分钟后重新响铃一样,避免因为自动更改状态值而影响正常运行。

发布了202 篇原创文章 · 获赞 4 · 访问量 4234

猜你喜欢

转载自blog.csdn.net/qq_44587855/article/details/103820270