Java多线程2:线程的使用及其生命周期
一、线程的使用方式
1、继承Thread类,重写父类的run()方法
优点:实现简单,只需实例化继承类的实例,即可使用线程
缺点:扩展性不足,Java是单继承的语言,如果一个类已经继承了其他类,就无法通过这种方式实现自定义线程
public class Thread01 extends Thread{
@Override
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
public class Test {
public static void main(String[] args) {
Thread thread = new Thread01();
thread.start();
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
结果:
......................
......................
main
main
main
main
main
Thread-0
Thread-0
Thread-0
......................
......................
可以看到两个线程并不是交替运行的,这也是正常的。解释之前先说一下多线程并发与并行的区别。
1、并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干片,使多个进程快速交替的执行。
如上图所示,并发就是只有一个CPU资源,程序(或线程)之间要竞争得到执行机会。图中的第一个阶段,在A执行的过程中,B、C不会执行,因为这段时间内这个CPU资源被A竞争到了,同理,第二阶段只有B在执行,第三阶段只有C在执行。其实,并发过程中,A、B、C并不是同时进行的(微观角度),但又是同时进行的(宏观角度)。
2、并行(parallellism):指在同一时刻,有多条指令在多个处理器上同时执行
如图所示,在同一时刻,ABC都是同时执行(微观、宏观)
通过多线程实现并发,并行:
➤ java中的Thread类定义了多线程,通过多线程可以实现并发或并行。
➤ 在CPU比较繁忙,资源不足的时候(开启了很多进程),操作系统只为一个含有多线程的进程分配仅有的CPU资源,这些线程就会为自己尽量多抢时间片,这就是通过多线程实现并发,线程之间会竞争CPU资源争取执行机会。
➤ 在CPU资源比较充足的时候,一个进程内的多线程,可以被分配到不同的CPU资源,这就是通过多线程实现并行。
➤ 至于多线程实现的是并发还是并行?上面所说,所写多线程可能被分配到一个CPU内核中执行,也可能被分配到不同CPU执行,分配过程是操作系统所为,不可人为控制。所以,如果有人问我我所写的多线程是并发还是并行的?我会说,都有可能。
➤ 不管并发还是并行,都提高了程序对CPU资源的利用率,最大限度地利用CPU资源
所以测试结果中两个线程并没有明显的交替运行就很好解释了。
所谓的多线程,指的是两个线程的代码可以同时运行,而不必一个线程需要等待另一个线程内的代码执行完才可以运行。对于单核CPU来说,是无法做到真正的多线程的,每个时间点上,CPU都会执行特定的代码,由于CPU执行代码时间很快,所以两个线程的代码交替执行看起来像是同时执行的一样。那具体执行某段代码多少时间,就和分时机制系统有关了。分时系统把CPU时间划分为多个时间片,操作系统以时间片为单位片执行各个线程的代码,越好的CPU分出的时间片越小。所以看不到明显效果也很正常,一个线程打印100句话本来就很快,可能在分出的时间片内就执行完成了(也不一定是并发或者是并行)。所以,最简单的解决办法就是把for循环的值调大一点就可以了(也可以在for循环里加Thread.sleep方法,这个之后再说)。
2、实现Runnable接口,重写run()方法,通过其实现类使用Thread
优点:
- 扩展性好,可以在此基础上继承其他类或实现其他接口,实现其他必需的功能
- 对于多线程共享资源的场景,具有天然的支持,适用于多线程处理一份资源的场景
缺点:构造线程实例的过程相对繁琐一点点
public class Thread01 implements Runnable{
@Override
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
public class Test {
public static void main(String[] args) {
Runnable runnable = new Thread01();
//通过Runnable接口的实现类构建Thread
Thread thread = new Thread(runnable);
thread.start();
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
结果:
......................
......................
main
main
main
main
main
Thread-0
Thread-0
Thread-0
......................
......................
如上所述,不再做分析。
3、实现Callable接口,重写call()方法,通过Runnable实现类使用Thread
public class Thread01 implements Callable<String>{ @Override
public String call() throws Exception {
return "hello world!!!";
}
}
public class Test {
public static void main(String[] args) {
Callable<String> callable = new Thread01();
//FutureTask是Runnable的实现类
FutureTask<String> futureTask = new FutureTask<String>(callable);
//通过Runnable接口的实现类构建Thread
Thread thread = new Thread(futureTask);
thread.start();
String result = null;
try {
result = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(result);
}
}
结果:
hello world!!!
优点:
- 扩展性好
- 支持多线程处理同一份资源
- 具备返回值以及可以抛出受检查异常
缺点:
- 相较于实现Runnable接口的方式,较为繁琐
我们对这三种方式进行分析,可以发现:方式一和方式二本质上都是通过实现Runnable接口并重写run()方法,将接口实现类的实例传递给Thread线程类来执行线程体(run()方法中的实现),这里将Runnable接口实现类的实例作为线程执行目标,供线程Thread实例执行;对于方式三,其实也是这样的,由于Thread类只能执行Runnable接口实现类的执行目标,所以需要对Callable接口的实现类进行包装,包装成Runnable接口的实现类(通过实现了Runnable接口的FutureTask类进行包装),从而使得Thread类能够接收Callable接口实现类的实例,可见这里使用了适配器模式!
综上所述,三种实现方式都存在着一个使用范式,即首先实现线程执行目标对象(包含线程所要执行的任务),然后将目标对象作为构造参数以实例化Thread实例,来获得线程!本质上都是实现一个线程体,由Thread来执行线程体,达到开启线程执行任务的效果!
三种实现方式对比的关键就在于extends和implements的对比,当然是后者好。因为第一,继承只能单继承,实现可以多实现;第二,实现的方式对比继承的方式,也有利于减小程序之间的耦合。因此,多线程的实现几乎都是使用的Runnable接口的方式!
二、线程的生命周期
虚拟机中的线程状态有六种,定义在Thread.State中:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW, /**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE, /**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED, /**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING, /**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING, /**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
1、New:新建状态
当线程实例被new出来之后,调用start()方法之前,线程实例处于新建状态。比如"Thread thread = new Thread()",thread就是一个处于NEW状态的线程。
2、Runnable:可运行状态
new出来线程,调用start()方法即处于Runnable状态了。处于Runnable状态的线程可能正在Java虚拟机中运行,也可能正在等待处理器的资源,因为一个线程必须获得CPU的资源后,才可以运行其run()方法中的内容,否则排队等待。
3、Blocked:阻塞状态
如果某一线程正在等待监视器锁,以便进入一个同步的块/方法,那么这个线程的状态就是阻塞Bloked。
4、Waiting:等待状态
某一线程因为调用不带超时的Object的wait()方法、不带超时的Thread的join()方法、LockSupport的park()方法,就会处于等待Waiting状态,等待被其它线程唤醒。
5、Timed_Waiting:超时等待状态
某一线程因为调用带有指定正等待时间(即传入时间参数)的Object的wait()方法、Thread的join()方法、Thread的sleep()方法、LockSupport的parkNanos()方法、LockSupport的parkUntil()方法,就会处于超时等待Timed_Waiting状态。
6、Terminated:中止状态
线程调用终止或者run()方法执行结束后,线程即处于终止状态。处于终止状态的线程不具备继续运行的能力。
线程的转换状态
上面也提到了,某一时间点线程的状态只能是上述6个状态中的其中一个;但是,线程在程序运行过程中的状态是会发生变化的,由一个状态转变为另一个状态,那么下面给出线程状态转换图帮助我们清晰地理解线程的状态转变过程:
上面我们已经对线程的实现以及线程的状态有了较为清晰的认识,那么通过上述内容,我们也可以发现其实有很多方法,我们并没有详细地介绍,比如start()、yield()、wait()、notify()、notifyAll()、sleep()、join()等等,这些方法大多来源于JDK中Thread类这一关键的线程类中,后续会结合Thread类的源码看一下,多线程编程中经常遇到的方法有哪些,以及这些方法的用途。
参考资料:
[Java 多线程技术](一)线程和进程以及并行和并发的概念
Java多线程2:线程的使用及其生命周期的更多相关文章
- Java多线程——线程的优先级和生命周期
Java多线程——线程的优先级和生命周期 摘要:本文主要介绍了线程的优先级以及线程有哪些生命周期. 部分内容来自以下博客: https://www.cnblogs.com/sunddenly/p/41 ...
- Java多线程之线程的生命周期
Java多线程之线程的生命周期 一.前言 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(R ...
- Java多线程| 01 | 线程概述
Java多线程| 01 | 线程概述 线程相关概念 进程与线程 进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位.可以把进程简单的理解 ...
- Java 多线程:线程池
Java 多线程:线程池 作者:Grey 原文地址: 博客园:Java 多线程:线程池 CSDN:Java 多线程:线程池 工作原理 线程池内部是通过队列结合线程实现的,当我们利用线程池执行任务时: ...
- Java多线程之线程其他类
Java多线程之线程其他类 实际编码中除了前面讲到的常用的类之外,还有几个其他类也有可能用得到,这里来统一整理一下: 1,Callable接口和Future接口 JDK1.5以后提供了上面这2个接口, ...
- Java多线程之线程的通信
Java多线程之线程的通信 在总结多线程通信前先介绍一个概念:锁池.线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池.每个对象都有自己的锁池的空间,用于放置等待运行的线程.这些 ...
- Java多线程之线程的同步
Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同 ...
- Java多线程之线程的控制
Java多线程之线程的控制 线程中的7 种非常重要的状态: 初始New.可运行Runnable.运行Running.阻塞Blocked.锁池lock_pool.等待队列wait_pool.结束Dea ...
- Java多线程父子线程关系 多线程中篇(六)
有的时候对于Java多线程,我们会听到“父线程.子线程”的概念. 严格的说,Java中不存在实质上的父子关系 没有方法可以获取一个线程的父线程,也没有方法可以获取一个线程所有的子线程 子线程的消亡与父 ...
- 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)
Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...
随机推荐
- Linux:Day24(下) samba
samba: smb:Service Message Block 是一种协议 CIFS:Common Internet File System smb --> samba 137/udp,138 ...
- 编写第一个 Shell 脚本
什么是 Shell 脚本? 一个 shell 脚本就是一个包含一系列命令的文件.shell 读取这个文件,然后执行 文件中的所有命令,就好像这些命令已经直接被输入到了命令行中一样. 怎样编写一个 Sh ...
- go语言程序设计学习笔记-1
https://www.jb51.net/article/126998.htm go标准库文档https://studygolang.com/pkgdoc 1. 如果想要再本地直接查看go官方文档,可 ...
- day14(2)---列表推导式、生成器推导式、字典推导式、三元表达式
一.列表推导式: ls = [元素表达式 for i in 可迭代对象 if 筛选条件] 案例: # -*- coding: utf-8 -*- '''列表生成式练习''' # 练习一(三元表达式): ...
- pycharm2018.3版 永久激活
pycharm2018.3版 永久激活 如需转发,请注明出处:小婷儿的python https://www.cnblogs.com/xxtalhr/p/10258257.html 激活前准备工作 ...
- Linux:CentOS7.4新建用户并授权
之前买了一台阿里云服务器,准备用来搭建一些服务,由于使用root用户登录进行操作比较敏感,就新建了一个用户,用来登录并进行日常操作. 这篇博客,介绍下centos7.4下如何新建用户并且授权... 一 ...
- 深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 3
备注: 因为文章太长,所以将它分为三部分,本文是第三部分. 第一部分:深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 1 第二部分:深入浅出经典面试题:从浏览器中输入URL ...
- Python-正则表达式总结版
前言: 总是写不好正则表达式,时间长不用就有些忘记了,故此在总结一篇文章以便日后查阅. 一.常用的匹配规则总结表 模式 描述 \w 匹配字母数字及下划线 \W 匹配非字母数字及下划线 \s 匹配任意空 ...
- python线程中的全局变量与局部变量
在python多线程开发中,全局变量是多个线程共享的数据,局部变量是各自线程的,非共享的. 如下几种写法都是可以的: 第一种:将列表当成参数传递给线程 from threading import Th ...
- H5 颜色属性
07-颜色属性 我是段落 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...