在之前了解了JAVA多线程的两种创建与启动方式之后,今天要说的是结束线程,以及对线程的控制,话不多说,先看看结束线程。
假如有这样的一个线程类:
不难看出如果该线程一旦启动,就会无限循环,而这时我们想通过另一个线程让其停止,比如主线程:
这里我在主线程中写了一个循环语句,当i的值等于100时,调用stop()方法终止线程,但很明显,stop()方法上划上了一条横线,说明该方法不再推荐使用,因为其存在不安全性。我们可以这样来写:
通常来说,线程执行体中的功能是持续性的,我们会将一些持续执行的语句放在线程执行体中,比如上面的线程类中我们循环打印的语句块,那么换句话讲,循环结束,也就意味着线程的结束,只要控制住循环,也就控制住了线程,这种方式也被称为通知方式,究竟如何做,我们来看一看。
既然问题变成了控制循环,我们可以先声明一个标记:
public boolean flag=true;
循环依旧是死循环:
@Override
public void run() {
while (flag){
System.out.println(Thread.currentThread().getName()+":"+i++);
}
}
再编写一个方法,用于改变flag的值:
public void setFlag(){
flag=false;
}
总的来说:
那么我们在test类中,这样写即可:
为了让效果明显,我将主线程中的循环语句打印了一个比较大的数,当i等于我们指定的一个数值时,调用之前写好的setFlag方法,将子线程中的flag改为false,从而退出循环完成线程的结束。
接下来我们说一说线程的控制,由于JAVA的多线程是抢占式,即高优先级的线程抢占CPU资源,在这里我们先说一说Thread类中的几个方法:
第一个是Sleep方法
sleep(long millis):是一个静态方法,使当前执行线程进入睡眠状态
同样我们使用上面类似的例子,首先是线程类:
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
//*e.printStackTrace();通常不做处理,用于唤醒睡眠状态的线程*//*
}
while (i<=100){
System.out.println(Thread.currentThread().getName()+":"+i++);
}
}
使用sleep方法需要try-catch处理异常,而一般我们不对异常进行处理,因为该异常会唤醒睡眠状态的线程,往后我们会看到效果。由于毫秒时间比较短暂,设置长一点时间比较容易看到效果,这里我们就设置5秒钟的睡眠。
接下来是Test类:
我们会发现点击运行后,过了5秒钟后控制台才开始打印输出线程体中的功能。
如果我们将sleep方法写入循环体:
则运行效果就是每隔一秒钟打印一次数字。
既然有睡眠,那么就有唤醒方法:
interrupt():中断阻塞(睡眠)状态的线程
比如在上面的例子中,我们将线程睡眠5秒后再执行,调用interrupt()方法即可唤醒:
t2.interrupt();
而该方法是通过抛出异常来唤醒睡眠状态的线程,也就是上面我们没有进行处理的异常,我们将其注释打开再运行:
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
运行结果:
JVM会以抛出一个异常的方式,告诉我们该睡眠的线程已经被唤醒,因此我们一般不对该异常做处理。
而如果我们将sleep()方法放入循环语句中:
再执行:
发现线程在一次被唤醒后,又进入了睡眠状态,这表示interrupt()方法是用于判断当前线程是否处于睡眠状态,当线程睡眠时,唤醒,而由于该线程是一个循环的,而interrupt()方法只调用了一次,所以对后面的睡眠状态不再唤醒,那么假如我们想要实现只要线程睡眠就进行唤醒,应该怎么写呢?
一种方法是,我们只要能循环去调用唤醒方法即可,那么我们可以写下一个死循环:
但这样的方式也会有问题,因为该循环语句会一直执行,即使线程已经处于唤醒状态了,该线程永远不会停下来,因此我们有了另一个方法:
isAlive():判断当前线程是否处于存活状态
我们将该方法放入while循环即可:
这样只有当线程处于存活状态时,该循环语句才会一直执行。
类似于sleep()方法,还有一个方法与其功能相同,但用法不同:
join() /join(long millis):是一个实例方法,使当前执行线程进入阻塞状态
两者不同在于一个是实例方法一个是静态方法,我们看看这个方法怎么用,同样还是上面的例子:
我们在主线程中加上一个打印100至200的循环语句,并将join方法加入,同样不处理异常:
反复运行后我们会发现,执行的顺序如下:
主线程会在子线程结束后才开始运行,也就是说明主线程被阻塞了,那么再回到join方法:
t2.join();
我们可以简单的理解为,子线程t2向正在运行的线程,也就是主线程申请了调用CPU的使用权,当t2执行完后,主线程再进行执行,由于我们并没有在括号内填入毫秒数,所以t2会执行完才让主线程执行,那如果我们在括号内填入毫秒数,那么就意味着t2会在我们指定的时间内先调用CPU,时间到了之后主线程就会恢复执行。
最后,我们再说一个不是很常用的方法:
yield():线程让步
简单来说,由于JAVA的多线程是抢占式,线程会互相抢占任务,而yield方法则是用于暂缓当前线程,让其他的线程更有机会执行,由于效果不是很明显,就不演示了。