线程的启动和终止
1、线程的构造
在运行线程之前首先要构造一个线程对象,java.Lang.Thread中为我们提供了一个用于创建线程时的初始化方法。主要对线程中的属性进行初始化
主要的属性
ThreadGroup g:线程组
Runnable target:可以调用run方法的对象
String name:构造的线程名字
long stackSize:新线程所需的堆栈大小,参数为0时表示被忽略
主要源码解析
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();
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;
tid = nextThreadID();
}
这表明一个新的构造的线程对象是由其parent线程来进行空间分配的,而child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的ID来标识这个child线程,至此一个能够运行的线程就初始化好了。
讲解了初始化方式后,我们来学习下怎么多线程编程?有哪些方式?这些方式之间有什么特点?
1.1继承Thread类
创建一个类去继承Thread类,重写里的run方法,在main方法中调用该类的实例对象的start方法就可以实现多线程的并发
测试代码
**
* @Author: Simon Lang
* @Date: 2020/5/2 17:05
*/
public class UseThread {
public static void main(String[] args){
MyThread thread1=new MyThread("线程A");
MyThread thread2=new MyThread("线程B");
thread1.start();
thread2.start();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name=name;
}
@Override
public void run() {
for (int i=1;i<=3;i++){
System.out.println("线程"+name+" :"+i);
}
}
}
测试结果
第一次测试
第二次测试
我们测试了两次结果,因为线程是并发执行的,所以结果可能是不同的,运行的结果与代码的执行顺序或者调用顺序无关。
1.2实现Runnable接口
* @Author: Simon Lang
* @Date: 2020/5/2 17:12
*/
public class UseRunnable {
public static void main(String[] args){
MyRunnable runnable1=new MyRunnable("线程C");
MyRunnable runnable2=new MyRunnable("线程D");
Thread thread1=new Thread(runnable1);
Thread thread2=new Thread(runnable2);
thread1.start();
thread2.start();
}
}
class MyRunnable implements Runnable{
private String name;
public MyRunnable(String name){
this.name=name;
}
public void run() {
for (int i=1;i<=3;i++){
System.out.println(name+" :"+i);
}
}
}
测试的结果也不是固定的,这里就不贴图了,这里也用到了Thread类,它的作用是把run方法包装成线程执行体,被包装后可以使用start方法执行线程。
Note:继承Thread类只能是单继承,如果实现Runnable接口,可以使得开发更加的灵活。
2、启动线程
线程的启动时调用start()方法,此时的线程会进入就绪状态,这表明它可以由JVM调度调度并执行,但是这并不意味着它会立即执行,当CPU给这个线程分配时间片后,就会开始run方法。
我们里来查看看satrt方法的源码看看都具体做了些什么
start方法源码解析
start()方法执行流程:①判断当前线程是不是首次创建②如果是,调用strat0方法(JVM)进行资源调度,回调run方法执行具体的操作
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
那么,我们可能会有疑问,为什么不能直接调用run方法呢?
这是因为start方法是用于启动线程的,可以实现并发,而run方法只是一个普通的方法,是不能实现并发的,只是在并发执行时调用
NOTE: start()方法中的native方法是本地方法,使用C/C++写的,这个方法的使用与java平台无关,java所做的只是将Thread对象映射到操作系统所提供的线程上面,对外提供统一的接口。
3、线程的中断
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其它线程进行了中断操作,但是通过中断并不能直接终止另一个线程,而是需要中断的线程自己处理。
java.lang.Thread类提供了几个方法操作中断状态:
测试当前线程是否被中断,该方法可以消除线程的中断状态,如果连续调用该方法,第二次的调用会返回false。
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
测试线程是否已经中断,中断的线程返回true,中断的状态不受该方法的影响
public boolean isInterrupted() {
return isInterrupted(false);
}
中断线程,将中断状态设置为true,
public void interrupt() {
}
Note:这三个方法中,只有interrupt()方法是修改中断标志的,另外的两个检测方法都是用于检测中断状态的
线程共有6中状态,中断主要用于运行态/阻塞态/等待态/超时等待,这四种状态的中断机制下又可分为两类。一类是设置中断标志,让被中断的线程判断后执行相应的代码。另一类是遇到中断时抛出InterruptedException异常,并清空中断标志位。
3.1运行态的中断/阻塞态中
对处于运行态和阻塞态执行中断后,它们的执行状态不变,但是中断标志位已被修改,并不会实际的中断线程的运行,我们可以利用java程序中的中断标志位来进行自我中断,因为这两种状态的操作都是类似的,所以我们只讲解运行态。
测试代码
public class TestInterrupte {
public static void main(String[] args) throws InterruptedException {
MyThreadA thread=new MyThreadA();
thread.start();
System.out.println(thread.getState());
System.out.println(thread.isInterrupted());
thread.interrupt();
System.out.println(thread.isInterrupted());
System.out.println(thread.getState());
}
}
class MyThreadA extends Thread{
@Override
public void run() {
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println("该线程执行中断");
break;
}
}
}
}
测试结果
Note:从测试结果可以看出,线程的中断不会立马影响线程的状态,线程中断前默认标志位为false,中断后标志位被修改true,标志后被修改后,子线程并没有马上执行中断,而是在主线程继续执行一段时间后才执行中断(从先打印RUNNABLE,后打印“该线程执行中断”可以看出)。
3.2等待态的中断/超时等待态的中断
这两种状态很类似,它们均是在线程运行的过程中缺少某些条件而被挂载在某个对象的等待对列中,当这些线程遇到中断操作的时候,就会抛出一个InterruptedException异常,并清空中断标志位,这里以等待态为例编写测试代码
测试代码
public class TestInterrupte {
public static void main(String[] args) throws InterruptedException {
MyThreadB thread=new MyThreadB();
thread.start();
MyThread.sleep(500);
System.out.println(thread.getState());
System.out.println(thread.isInterrupted());
thread.interrupt();
ThreadB.sleep(500);
System.out.println(thread.isInterrupted());
System.out.println(thread.getState());
}
}
class MyThreadB extends Thread{
@Override
public void run() {
synchronized (this){
try {
wait();
}catch (InterruptedException e){
System.out.println("发生中断了,我要抛出异常");
}
}
}
}
测试结果
NOTE:从结果上可以看出,线程被启动后就被挂载到了等待队列中了,我们执行中断后,输出了我们要打印的异常语句,因为标志位被清空,所以打印出来的标志位时false,执行完中断后立马进入到阻塞态。
中断总结:
线程中断不会使得线程立马退出,而是会给线程发送一个通知,告诉目标线程你需要退出了,具体的退出操作是由目标线程来执行,这也就是为什们上面测试代码中,从等待态--->阻塞态的原因
4、线程的终止
在早期的jdk版本时,经常使用stop方法来强制终止线程,但是这种操作是不安全的,会导致数据的丢失,所以,可以使用interrupt来中断线程,除此之外,还可以利用中断标志使线程正常退出,也就是当run方法执行完后终止线程。
所以总结来说,线程的终止有三种方式
stop方法强制退出
interrupt方法中断线程
使用退出标志
因为第一种方法不安全,本文将重点讲解是由退出标志位进和使用中断来终止线程
测试代码
public class ShutDown {
public static void main(String[] args) throws InterruptedException {
Runner one=new Runner();
Thread countThread=new Thread(one,"CountThread");
countThread.start();
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();
Runner two=new Runner();
countThread=new Thread(two,"CountThread");
countThread.start();
TimeUnit.SECONDS.sleep(1);
two.cancel();
}
private static class Runner implements Runnable{
private long i;
private volatile boolean on=true;
public void run() {
while (on && !Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("Count i= " +i);
}
public void cancel(){
on=false;
}
}
}
NOTE:程序创建了线程CountThread,它不断的进行变量的累加,而主线程尝试对其进行中断操作和停止操作。分别使用了cancel方法和中断操作来终止线程,这两种方法都是有效的。
参考文献
[1]方腾飞.java并发编程的艺术
[2]https://blog.csdn.net/weixin_43490440/article/details/101519957
[3]https://www.cnblogs.com/yangming1996/p/7612653.html
评论 (4 条评论)