Java并发基础-启动和终止线程

简介: Java并发基础-启动和终止线程

构造线程

在运行线程之前首先要构造一个线程对象,线程对象在构造的时候需要提供线程所需要 的属性,如线程所属的线程组、线程优先级、是否是Daemon线程等信息。

private void init(ThreadGroup g, Runnable target, String name,long stackSize,
            AccessControlContext acc) {
    if (name == null) {
      throw new NullPointerException("name cannot be null");
    }
              // 当前线程就是该线程的父线程
    Thread parent = currentThread();
    this.group = g;
               // 将daemon、priority属性设置为父线程的对应属性
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    this.name = name.toCharArray();
    this.target = target;
    setPriority(priority);
              // 将父线程的InheritableThreadLocal复制过来
    if (parent.inheritableThreadLocals != null)
      this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.
          inheritableThreadLocals);
                // 分配一个线程ID
    tid = nextThreadID();
  }

在上述过程中,一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程 继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的 ThreadLocal,同时还会分配一个唯一的ID来标识这个child线程。至此,一个能够运行的线程对 象就初始化好了,在堆内存中等待着运行。

启动线程

线程对象在初始化完成之后,调用start()方法就可以启动这个线程。线程start()方法的含义 是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用 start()方法的线程。

启动一个线程前,最好为这个线程设置线程名称,因为这样在使用jstack分析程 序或者进行问题排查时,就会给开发人员提供一些提示,自定义的线程最好能够起个名字。

理解中断

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行 了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt() 方法对其进行中断操作。

线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否 被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。如果该 线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返 回false。

从Java的API中可以看到,许多声明抛出InterruptedException的方法(例如Thread.sleep(long millis)方法)这些方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。

如下的代码,首先创建了两个线程,SleepThread和BusyThread,前者不停地睡眠,后者一直运行,然后对这两个线程分别进行中断操作,观察二者的中断标识位。

public class Interrupted {

    public static void main(String[] args) throws Exception {
        // sleepThread不停的尝试睡眠
        Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
        sleepThread.setDaemon(true);
        // busyThread不停的运行
        Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
        busyThread.setDaemon(true);
        sleepThread.start();
        busyThread.start();
        // 休眠5s,让sleepThread和busyThread充分运行
        TimeUnit.SECONDS.sleep(5);
        sleepThread.interrupt();
        busyThread.interrupt();
        System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
        System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
        //防止sleepThread和busyThread立刻退出
        TimeUnit.SECONDS.sleep(2);
    }

    static class SleepRunner implements Runnable {
        @Override
        public void run() {
            while (true) {
                SleepUtils.second(10);
            }
        }
    }

    static class BusyRunner implements Runnable {
        @Override
        public void run() {
            while (true) {
            }
        }
    }
}

输出如下

SleepThread interrupted is false
BusyThread interrupted is true

从结果可以看出,抛出InterruptedException的线程SleepThread,其中断标识位被清除了, 而一直忙碌运作的线程BusyThread,中断标识位没有被清除

过期的suspend(),resume()和stop()

大家对于CD机肯定不会陌生,如果把它播放音乐比作一个线程的运作,那么对音乐播放做出的暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。

如下代码中,创建了一个线程PrintThread,它以1秒的频率进行打印,而主线程对其进行暂停、恢复和停止操作。

public class Deprecated {
    @SuppressWarnings("deprecation")
    public static void main(String[] args) throws Exception {
        DateFormat format = new SimpleDateFormat("HH:mm:ss");
        Thread printThread = new Thread(new Runner(), "PrintThread");
        printThread.setDaemon(true);
        printThread.start();
        TimeUnit.SECONDS.sleep(3);
        // 将PrintThread进行暂停,输出内容工作停止
        printThread.suspend();
        System.out.println("main suspend PrintThread at " + format.format(new Date()));
        TimeUnit.SECONDS.sleep(20);
        // 将PrintThread进行恢复,输出内容继续
        printThread.resume();
        System.out.println("main resume PrintThread at " + format.format(new Date()));
        TimeUnit.SECONDS.sleep(3);
        // 将PrintThread进行终止,输出内容停止
        printThread.stop();
        System.out.println("main stop PrintThread at " + format.format(new Date()));
        TimeUnit.SECONDS.sleep(3);
    }

    static class Runner implements Runnable {
        @Override
        public void run() {
            DateFormat format = new SimpleDateFormat("HH:mm:ss");
            while (true) {
                System.out.println(Thread.currentThread().getName() + " Run at " + format.format(new Date()));
                SleepUtils.second(1);
            }
        }
    }
}

输出如下(输出内容与执行的具体时间有关)

PrintThread Run at 17:34:36
PrintThread Run at 17:34:37
PrintThread Run at 17:34:38
main suspend PrintThread at 17:34:39
main resume PrintThread at 17:34:42
PrintThread Run at 17:34:42
PrintThread Run at 17:34:43
PrintThread Run at 17:34:44
main stop PrintThread at 17:34:45

在执行过程中,PrintThread运行了3秒,随后被暂停,3秒后恢复,最后经过3秒被终止。

通过示例的输出可以看到,suspend()、resume()和stop()方法完成了线程的暂停、恢复和终 止工作,而且非常“人性化”。但是这些API是过期的,也就是不建议使用的。

不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资 源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结 一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会, 因此会导致程序可能工作在不确定状态下。

正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建 议使用的过期方法,而暂停和恢复操作可以用后面提到的等待/通知机制来替代。

安全的终止线程

中断状态是线程的一个标识位,而中断操作是一种简便的线程间交互 方式,而这种交互方式最适合用来取消或停止任务。除了中断以外,还可以利用一个boolean变 量来控制是否需要停止任务并终止该线程

如下,创建了一个线程CountThread,它不断地进行变量累加,而 主线程尝试对其进行中断操作和停止操作。

public class Shutdown {
    public static void main(String[] args) throws Exception {
        Runner one = new Runner();
        Thread countThread = new Thread(one, "CountThread");
        countThread.start();
        // 睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束
        TimeUnit.SECONDS.sleep(1);
        countThread.interrupt();
        Runner two = new Runner();
        countThread = new Thread(two, "CountThread");
        countThread.start();
        // 睡眠1秒,main线程对Runner two 进行取消,使CountThread能够感知on为false而结束
        TimeUnit.SECONDS.sleep(1);
        two.cancel();
    }

    private static class Runner implements Runnable {
        private long             i;

        private volatile boolean on = true;

        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()) {
                i++;
            }
            System.out.println("Count i = " + i);
        }

        public void cancel() {
            on = false;
        }
    }
}

输入如下

Count i = 543487324
Count i = 540898082

示例在执行过程中,main线程通过中断操作和cancel()方法均可使CountThread得以终止。 这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地 将线程停止,因此这种终止线程的做法显得更加安全和优雅。


目录
相关文章
|
10天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
1天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
19小时前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
13 1
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
23 4
|
5天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
25 3
|
6天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
18 1
|
9天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
23 2
|
10天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
17 3
|
10天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
10 2