并发编程系列之线程的启动终止

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 并发编程系列之线程的启动终止

并发编程系列之线程的启动终止

前言

上节我们对线程有了个基本的概念和认识,从线程状态转变过程我们也已经知道了线程通过调用start方法进行启动,直到run方法执行线程结束,今天我们就来详细的说说启动和终止线程的细节,OK,让我们开始今天的并发之旅吧。

并发编程系列之线程的启动终止

创建线程

在使用一个线程之前我们需要先构造线程,即new一个线程


Thread thread = new Thread();

线程对象在构建的时候需要提供线程所需要的属性,如线程组、优先级等等,下面我们看下如下的源代码:


private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        // 如果新线程名字为null,抛出异常
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        // 获取该线程的父线程
        Thread parent = currentThread();
        // 获取安全管理组件
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            // 如果安全组件不为空,就调用SecurityManager的线程组
            if (security != null) {
                g = security.getThreadGroup();
            }
            // 如果SecurityManager为空,并且该线程的线程组也为空,则调用其父线程的线程组
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        // 显示通过 允许线程访问线程组
        g.checkAccess();

        // 检查访问权限
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        // 对线程组中未使用的线程计数器+1
        g.addUnstarted();
        // 调用父线程的线程组
        this.group = g; 
        // 调用父线程守护线程
        this.daemon = parent.isDaemon(); 
        // 调用父线程的优先级
        this.priority = parent.getPriority(); 
        // 将字符串转换为新的字符数组
        this.name = name.toCharArray();
       // 加载父线程的ContextClassLoader
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext = AccessController.getContext();
        this.target = target;
        setPriority(priority);
       // 加载父线程的ThreadLoad
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        // 指定堆栈大小
        this.stackSize = stackSize;
        // 设置线程ID
        tid = nextThreadID();
    }

从上面源码我们可以看到,一个新的线程是由其父线程来进行空间分配的,子线程继承父线程的优先级,是否为守护线程、contextClassLoader以及ThreadLocal,最后分配一个唯一的线程ID,新的线程就被创建完毕,在堆内存中等待着被运行。

也可以通过构建Runnable对象来构建线程:


Thread t1 = new Thread(new Runnable() {
      @Override
      public void run() {
          ...
      }
    }, "t1");
并发编程系列之线程的启动终止

启动线程

线程创建完毕,我们就可以开始使用该线程了,启动一个线程很简单,直接使用start方法


thread.start();

我们再来对start方法的源码进行分析:


/**
 * 该方法不是给main线程和系统线程调用的
 * 由虚拟机创建和设置的组线程
 */
public synchronized void start() {
        // 0代表线程的状态NEW,初始化状态,如果线程不是初始化状态,则抛出异常
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        
        // 通知线程组,该线程即将被启动,添加到组的运行线程列表中,同时未使用线程数计数器-1
        group.add(this);

        // 启动成功标识符
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    // 如果启动不成功,线程组做相应启动失败处理
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                // 如果什么都不做,则
            }
        }
    }

执行start方法之后,当前线程(父线程)就会同步通知虚拟机,只要线程规划器空闲,就应该立即启动该线程(调用start方法的线程)。

并发编程系列之线程的启动终止

线程中断

线程中断表示一个运行中的线程是否被其他线程进行中断操作,中断在线程中是由一个Boolean值来标识的,我们看下如何判断当前线程中断状态和相关操作:

  • 判断线程是否被中断:isInterrupted方法
  • 
    public static void main(String[] args) throws Exception{
            Thread thread1 = new Thread();
            thread1.start();
            System.out.println("线程:"+thread1.getName()+"是否被中断:"+thread1.isInterrupted());
        }
    结果:线程:Thread-0 是否被中断:false
    
  • 中断线程方法:interrupt方法
  • 
    thread1.interrupt();
    System.out.println("线程:"+thread1.getName()+"是否被中断:"+thread1.isInterrupted());
    
    结果:线程:Thread-0是否被中断:true
    
  • 清除中断标识:currentThread().interrupt()方法
  • 
    public static void main(String[] args) throws Exception{
            Thread thread1 = new Thread();
            thread1.start();
            thread1.interrupt();
            System.out.println("线程:"+thread1.getName()+"是否被中断:"+thread1.isInterrupted());
            thread1.currentThread().interrupt();
            System.out.println("线程:"+thread1.getName()+"是否被中断:"+thread1.isInterrupted());
        }
    结果:
    线程:Thread-0是否被中断:true
    线程:Thread-0是否被中断:false
    

    安全的终止线程

    上面提到的中断方式是一种比较常见的终止方式,除此之外还有2种方式,一个是使用一个标志位来通知线程终止,还有一个就是使用stop方法(不推荐,下面会详解),下面我们先看看使用标志位和中断如何终止线程:

  • 使用标志位Boolean值
  • 
    public class ThreadStartDemo {
        // 线程终止标识位
        static volatile Boolean flag = false;
        static int time1 = 0;
        static int time2 = 0;
    
        public static void main(String[] args) throws Exception {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程1启动");
                    while (!flag) {
                    }
                    time2 = Integer.parseInt(DateUtil.getNowTimestamp());
                    int i = time2 - time1;
                    System.out.println("线程t1退出,等待时间为" + i + "秒");
                }
            }, "t1");
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    time1 = Integer.parseInt(DateUtil.getNowTimestamp());
                    System.out.println("线程2启动,5秒后修改flag的值");
                }
            }, "t2");
    
            t1.start();
            t2.start();
            t2.sleep(5000);
            flag = true;
        }
    }
    

    运行结果如下:

    并发编程系列之线程的启动终止

    5秒过后:注意看,线程以及终止运行

    并发编程系列之线程的启动终止
  • 使用中断机制终止线程

  • 
    public class ThreadExitDemo extends Thread {
        public void run() {
            System.out.println("线程运行中!!!");
            System.out.println("请输入任意键盘值来发出中断信号");
        }
    
        public static void main(String[] args) throws Exception {
            Thread thread = new ThreadExitDemo();
            thread.start();
            System.in.read();
            thread.interrupt();
            thread.join();
            System.out.println("线程已经退出!!!");
        }
    }
    

    运行结果如下:

    并发编程系列之线程的启动终止 并发编程系列之线程的启动终止 并发编程系列之线程的启动终止

    线程中几个废弃的方法

    在线程运行过程中还有三个被抛弃的方法,分别是suspend()、resume()、stop()方法,分别代表暂停、恢复和停止的意思,那么为什么这3个方法被废弃了呢?

  • suspend方法在被调用后,线程不会释放已经占有的资源(如锁),而是占着资源进入睡眠状态,这样容易引起死锁问题;
  • resume和suspend是成对出现的,既然suspend被抛弃了,当然好基友resume也就没有用武之地了,也是不被推荐使用的方法;
  • stop方法在终止一个线程时,不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,这就会导致程序可能工作在不确定的状态下,导致一个线程任务不能完整的执行完就被直接终止,程序执行的正确性得不到保证;
  • resume和suspend是成对出现的,既然suspend被抛弃了,当然好基友resume也就没有用武之地了,也是不被推荐使用的方法;

    以上就是今天所讲的线程启动和终止的相关内容,希望通过这篇文章,你能对如何正确的启动和关闭一个线程有所掌握,感谢您的阅读!!!

    并发编程系列之线程的启动终止

    相关文章:

    原文始发于微信公众号(Justin的后端书架):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    转载声明:转载请注明出处,本技术博客是本人原创文章

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 并发编程系列之线程的启动终止


     上一篇
    并发编程系列之线程之间的通信 并发编程系列之线程之间的通信
    前言 上节我们介绍了线程从创建到结束的过程,介绍了几种常见的启动和终止方法,我们知道了如何使用一个线程,那么今天我们再接下来看看1个或者多个线程之间是如何进行通信的?OK,让我们一起走进今天的并发之旅吧,祝您旅途愉快。 景点一:
    2021-04-05
    下一篇 
    并发编程系列之线程简介 并发编程系列之线程简介
    前言 前几天我们把Java内存模型介绍了下,大家对JMM也有所认识了,从今天我们就开始走进一个我们天天挂在嘴边,听在耳边的东西:线程,对于线程相信大家都不会陌生,当然也有很多小伙伴在开发中或多或少的使用到线程,即使你没有使用过,但是并不
    2021-04-05