java多线程系列(2)线程生命周期和常见api

简介: 上一篇文章对java线程的一些相关概念,进行了一个认识,并且还举出了一个基本的案例,这篇文章我们将对线程的常用API和生命周期进行一个讲解分析。

一、从最简单的例子说起


再开始讲解java线程的api我们还需要先对线程有一个回顾和了解。对此,给出一个最基本的线程案例。

public class MyThread extends Thread {
    private String name;
    public MyThread(String name) {
        super();
        this.name = name;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(name + ":正在执行!" + i);
        }
    }
}

我们在这里定义了一个线程,然后在run方法中循环输出i。然后我们就可以像使用其他类一样去使用了。

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("线程一");
        myThread.start();
    }
}

这就是一个最简单的例子,我们可能会想,线程好像也没啥特殊的,new出来一个对象然后执行就结束了。那他的生命周期会不会也是和普通类一样呢?再继续往下看:


二、线程的生命周期


1、思考一个问题


在上面我们new出来一个线程实例,然后调用start方法之后,这个线程就开始执行了嘛?

在上一篇文章曾经说过,这和CPU的时间片轮询有关,CPU需要在多条线程之间切换,轮到他他才会执行。因此呢,当调用start方法之后,线程只能说有可能在执行。


2、线程的生命周期


对于线程来说,它的一生要经过五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(terminated)。


在网上看过N多个版本的线程生命周期状态图,自己也使用画图工具花了一个。

v2-7f44b575ada3666f21f12120e23e14c6_1440w.jpg

这张图基本上涵盖了线程的整个生命周期。上面的123456序号表示实现状态之间转换的方法,比如说调用了线程的某些API方法,或者是外部环境的一些改变,都能够使得线程从一个状态转变到另外一个状态,下面我们对这五个状态分别来解释一下;


(1)new状态

也就是说,在这里我们只是实例化了线程,还没有start。此时的Thread和普通的类完全没有区别。因为在这里new出来一个对象却没有使用,什么也干不了。start方法被调用之后就进入了框框里面的两个状态。

(2)runnable状态

这个状态表示没有获取CPU执行权,也就是CPU时间片还没有轮询到这个线程,此时的线程表示有了能够执行得能力,但是还没有轮到他执行而已。

(3)running状态

这个状态表示获得了CPU的执行权,然后线程里面的run方法就开始执行了。

(4)blocked状态

这个状态表示的是阻塞状态,为什么会出现这个状态呢?有常见的3个原因:

原因一:从running状态转变而来,因为调用了线程调用了sleep或者是wait方法。

原因二:突然执行了IO操作,比如文件保存或者是读取了网络文件等等。

原因三:想要获取某一个锁,但是当前获取锁的线程太多,于是去排队了。

在这个blocked状态中,可以直接到terminated状态或者是回到running状态。

(5)terminated状态

这个状态表示线程的生命周期就要结束了。

在这里上面的123456里面的内容,在我们讲解线程api的时候再进行理解,因为在这里直接列出来是什么原因造成的状态之间切换的话,到了下面就忘了。


注意:这张图时刻牢记着


三、线程常见的API


1、休眠sleep


这种方式会使得线程从running状态转变到runnable状态或者是blocked状态。

用法很简单,直接让线程去调用执行就OK了,会抛出一个InterruptedException异常:

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("线程一");
        myThread.start();
        try {
            //表示会休眠1秒钟
            myThread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

不过目前的这种做法正在被TimeUnit所取代,比如我们想要休眠的话,我们可以直接这样:

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("线程一");
        myThread.start();
        try {
            //单位是秒,直接写1即可
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

当然还有分钟、小时、天等单位。


2、中断interrupt


这种方式会线程blocked状态转变到其他状态,比如说runnable、terminated和running状态。

意思是打断当前线程的阻塞状态,就好比你正在睡觉,突然有人把你吵醒了。代码演示一下:

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("线程一");
        //myThread企图休眠一分钟
        myThread.start();
        try {
            //单位是秒,直接写1即可
            TimeUnit.SECONDS.sleep(2);
            //休眠两秒就被中断了
            myThread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在myThread中企图休眠一分钟,但是在主线程main中,休眠两秒就被打断了,此时就会报错:

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.fdd.chapter2.MyThread.run(MyThread.java:17)

上面异常的意思就是睡眠被打断了。这个方法为什么能够中断线程呢?我们来深入的理解一下:


(1)中断线程相关的方法

在线程内部有一个中断flag,如果我们执行了interrupt方法,那么这个标签将会被置为true。表示此线程被打上了中断标记,与线程中断的api还有两个:

v2-fe367018c5af17da4e624cc6e73c932a_1440w.jpg

其中,interrupt 方法是唯一能将中断状态设置为 true 的方法。静态方法 interrupted 会将当前线程的中断状态清除,但这个方法的命名极不直观,很容易造成误解,需要特别注意。


(2)如何中断一个线程

在这里你可能有一个疑问,上面不是已经说调用中断方法就会中断一个线程?你先来看看下面中断失败的例子:

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
            }
        });
        thread.start();
        //我们尝试中断这个线程
        thread.interrupt();
    }
}

我们尝试中断这个线程,会发现不起任何作用,这是因为你只是去中断线程,但是线程里面没有对其进行回应,就好比说别人在吃饭,你说我要把你的饭端走,别人懒得理你,继续吃一样。


所以要想中断一个线程,不仅仅要给线程一个中断信号,线程也要能够积极地响应这个中断信号。

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {    
                // 响应中断信号
                if (Thread.currentThread().isInterrupted()) {
                    //判断当前线程是否被中断
                    return;
                }
            }
        });
        thread.start();
        thread.interrupt();
    }
}

上面的这个例子中,线程内部积极的相应了外部的中断信号,就能够中断线程了。现在相信你稍微有点豁然开朗了


3、yied方法


这种方式会使得线程从running状态转变到runnable状态。

意思是线程告诉调度器,愿意放弃当前的CPU资源,你可以去执行其他的线程了。当然调度器比较忙的时候,可以不用搭理这个线程的请求。

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("线程一");
        myThread.start();
        try {
            myThread.yield();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


4、线程优先级


(1)设置线程优先级


优先级高的话表示线程优先调用,但不是绝对的被优先调用。就好比是线程告诉调度器,想要更高的权限去获取CPU资源,但是调度器同样可以忽略他的请求。

优先级有1到10。数字越大优先级越大。

public class Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {     
            System.out.println("优先级4");
        });
        Thread thread2 = new Thread(() -> {
            System.out.println("优先级8");
        });
        thread1.setPriority(4);
        thread2.setPriority(8);
        thread1.start();
        thread2.start();
    }
}

在这里线程2就可能比线程1先执行。


(2)获取优先级


public class Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {});
        Thread thread2 = new Thread(() -> {});
        System.out.println(thread1.getPriority());
        System.out.println(thread2.getPriority());
        thread1.start();
        thread2.start();
    }
}

这两个线程的优先级如果不提前设置都是5.


5、join方法


此方法可以使其他线程从running状态转换到blocked状态。

join线程A之后,其他的线程必须要等待线程A执行结束才可以执行。此时其他的线程一直处于bloecked。我们可以看一下例子:

新建一个线程:

public class MyThread extends Thread {
    private String name;
    public MyThread(String name) {
        super();
        this.name = name;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(name+i);
        }
    }
}

然后我们使用一下join方法:

public class Test {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread("线程A");
        MyThread thread2 = new MyThread("线程B");
        thread1.start();
        try {
            thread2.start();
            thread2.join(); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里就是让线程B先执行完再执行线程A,但这里并不是绝对的,因为在start线程B之前,可能CPU刚好执行了线程A,于是输出了一个线程A,然后轮到线程B的时候,就会一下子全部输出。


6、getId获得线程ID


线程的ID在整个jvm中都是唯一的。

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{}) ;
        System.out.println(thread.getId());
    }
}

要注意,在这里输出的线程ID不是从0开始的,因为再执行一个程序的时候,jvm会默认开辟很多个 线程。


7、获取当前线程


public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{}) ;
        System.out.println(Thread.currentThread());
    }
}
//输出:
Thread[main,5,main]

以上就是常用的一些api。还有一个stop关闭线程的方法,但是一般不会用到。


到现在为止我们已经分析了其生命周期和常用的API,但是还有一点就是我们好像还没说过线程的源码,下一节就对线程的源码进行一个分析,最主要的就是其构造函数的使用,也揭晓一下为什么调用start方法就启动了一个线程。


相关文章
|
3天前
|
数据采集 JSON Java
Java爬虫获取微店快递费用item_fee API接口数据实现
本文介绍如何使用Java开发爬虫程序,通过微店API接口获取商品快递费用(item_fee)数据。主要内容包括:微店API接口的使用方法、Java爬虫技术背景、需求分析和技术选型。具体实现步骤为:发送HTTP请求获取数据、解析JSON格式的响应并提取快递费用信息,最后将结果存储到本地文件中。文中还提供了完整的代码示例,并提醒开发者注意授权令牌、接口频率限制及数据合法性等问题。
|
3天前
|
数据采集 存储 Java
Java爬虫获取微店店铺所有商品API接口设计与实现
本文介绍如何使用Java设计并实现一个爬虫程序,以获取微店店铺的所有商品信息。通过HttpClient发送HTTP请求,Jsoup解析HTML页面,提取商品名称、价格、图片链接等数据,并将其存储到本地文件或数据库中。文中详细描述了爬虫的设计思路、代码实现及注意事项,包括反爬虫机制、数据合法性和性能优化。此方法可帮助商家了解竞争对手,为消费者提供更全面的商品比较。
|
4天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
45 14
|
5天前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
34 20
|
7天前
|
缓存 Java 应用服务中间件
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
35 5
|
7天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
37 13
|
8天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
11天前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
24天前
|
算法 Java 程序员
菜鸟之路Day06一一Java常用API
《菜鸟之路Day06——Java常用API》由blue编写,发布于2025年1月24日。本文详细介绍了Java中常用的API,包括JDK7的时间类(Date、SimpleDateFormat、Calendar)和JDK8新增的时间API(ZoneId、Instant、DateTimeFormatter等),以及包装类的使用。通过多个实例练习,如时间计算、字符串转整数、十进制转二进制等,帮助读者巩固所学内容,提升编程技能。文章强调了理论与实践结合的重要性,鼓励读者多做练习以提高学习效率。
76 28
|
1月前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。

热门文章

最新文章