【多线程】多线程基础知识
文章目录
- 1. 认识线程(Thread)
- 2. Thread 类及常见方法
- 2.1 Thread 常见构造方法
- 2.2 start 和 run 方法的区别
- 2.3 Thread 常见属性
- 2.4 中断线程
- 2.5 等待线程
- 2.6 休眠线程
- 3. 线程的状态
- 3.1 线程的所有状态
- 3.2 线程状态转移图
1. 认识线程(Thread)
进程和线程的关系和区别:
线程是包含在进程中的,每个进程至少有一个线程存在,即主线程。
每个线程都是一个独立的执行流,可以各自执行自己的任务,多个线程之间“同时”执行多个任务。
同一个进程的每个线程之间公用同一份资源(包括虚拟地址空间+文件描述符)
线程A创建的变量,线程B可以用到。线程B打开的文件,线程A可以用到。
不同进程之间不能共享内存空间。
从操作系统的角度可以认为,进程是系统资源分配的基本单位,线程是系统调度执行的基本单位。
线程存在的意义:
由于并发编程成为刚需,虽然多进程能够实现并发编程,但是线程比进程更加轻量,能够更加充分的利用多核 CPU 的资源:
- 线程创建比进程创建更快
- 线程销毁比进程销毁更快
- 线程调度比进程调度更快
线程可以视为是轻量级进程,但随着创建和销毁频率提高,线程也就显得比较重了,进一步的解决方案如下:
- 使用线程池
- 使用协程,也叫做纤程
Java 的线程和操作系统线程的关系:
- 线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些 API 供用户使用。
- Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行性了进一步的抽象和封装。
2. Thread 类及常见方法
Thread 类是 JVM 用来管理线程的一个类,每个创建的 Thread 实例都是和系统中的一个线程是对应的,用来描述一个线程执行流,JVM 会将 Thread 对象组织起来,用于线性调度和线程管理。
2.1 Thread 常见构造方法
方法 | 说明 |
---|---|
Thread()
|
创建线程对象 |
Thread(Runnable target)
|
使用 Runnable 对象创建线程对象 |
Thread(String name)
|
创建线程对象并命名 |
Thread(Runnable target, String name)
|
使用 Runnable 对象创建线程对象,并命名 |
Tread(ThreadGroup group, Runnable target)
|
线程可以被用来分组管理,分好的组几位线程组 |
Java 中实现多线程主要有以下两种方式:
继承 Thread 类
实现 Runnable 接口
定义:
Runnable 接口时线程辅助类,仅定义了一个 fun() 方法。该方法中描述了要实现的线程的内容。
优点:
- Runnbale 可以继承其它类实现对 Runnable 实现类的增强,避免了直接继承 Thread 类而无法继承其他类的问题。
- Runnable 接口可以被多个线程共享,适用于多个进程处理一种资源的问题。
Thread 用法一:
创建一个类,继承自 Thread,重写 Thread 中的 run 方法,该 run 方法内部包含了这个线程要执行的代码,当线程跑起来时就会依次来执行这个 run 方法中的代码。
// 定义的这个类描述了要创建出的线程是啥样的,但是并未真正创建出来
class MyThread1 extends Thread{@Overridepublic void run() {System.out.println("hello thread!");}
}public class Demo1 {public static void main(String[] args) {// 1. 创建 Thread 实例,此处创建的 MyThread1MyThread1 t = new MyThread1();// 2. 调用 Thread 的 start 方法,这会在系统内部真正创建出线程t.start();}
}
Thread 用法二:
创建一个类,实现 Runnable 接口,重写 run 方法。创建 Thread 实例,然后把刚才的 Runnable 实例给设置进去。
class MyRunnable implements Runnable{@Overridepublic void run() {// 描述这个任务具体要进行的工作System.out.println("hello thread2!");}
}public class Demo2 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start();}
}
Thread 用法三:
在用法一的基础上改造,采用匿名内部类。
public class Demo3 {public static void main(String[] args) {Thread t = new Thread(){@Overridepublic void run() {System.out.println("hello thread3!");}};t.start();}
}
Thread 用法四:
在用法二的基础上改造,采用匿名内部类。
public class Demo4 {public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("hello thread4!");}});t.start();}
}
Thread 用法五:
在用法二的基础上改造,搭配 lambda 表达式。
public class Demo5 {public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("hello thread5!");});t.start();}
}
2.2 start 和 run 方法的区别
- 调用 start 方法能够在操作系统的底层创建一个新的线程,新线程中会执行 run 方法。
- 覆写 run 方法是提供线程要做的事情的指令清单,本身并不具备创建线程的能力。
如果对创建的线程对象调用 run 方法,那么并没有创建出一个新线程,并在新线程中执行指定的任务,而是在原本的线程中执行指定的任务,并只有在指定任务结束后才会执行后续的操作。
2.3 Thread 常见属性
属性 | 补充 | 获取方法 |
---|---|---|
ID | ID 是线程唯一标识 |
getId()
|
名称 | 名称是便于调试的,JVM 会默认指定名称 |
getName()
|
状态 | 状态表示线程当前所处的一个情况 |
getState()
|
优先级 | 理论上优先级越高越容易被调度到 |
getPriority()
|
是否后台线程 | JVM 会在一个进程的所有非后台线程结束后,才会结束运行 |
isDaemon()
|
是否存活 | 表示 run 方法是否结束运行,NEW 和 TERMINATED 状态是未存活。 |
isAlive()
|
是否被中断 |
isInterrupted()
|
|
返回当前线程对象的引用 |
以上属性在被调用时,要先通过 Thread 类的 currentThread() 方法获取到正在被执行的线程引用才能够调用。
|
currentThread()
|
补充:如果是通过继承 Thread 来创建的线程,此时直接在 run 方法中通过 this,就能够拿到这个线程的实例。
2.4 中断线程
线程只有执行完 run 方法中的内容才会结束,但如果该线程执行的内容是一个死循环,并且涉及到风险,那么就要能够手动停止线程了。
目前常见的中断线程的方式如下:
手动设置标志位,来作为循环结束的判定条件(需要给标志位加上 volatile 关键字,该方法存在缺陷)。
volatile 的作用如下:
- 保证变量的内存可见性
- 禁止指令重排序
public class Demo6 {// 通过这个变量来控制线程是否结束public volatile static boolean isQuit = true;public static void main(String[] args) {Thread t = new Thread(() -> {while(isQuit) {System.out.println("hello thread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}isQuit = false;System.out.println("hello main!");} }
借助 Thread 实例中自己提供的一个标志位,来作为循环结束的判定条件,方式如下:
方法 说明 public void interrupt()
中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位为 true。 public static boolean interrupted()
判断当前线程的中断标志位是否设置,调用后清楚标志位。 public boolean isInterrupted()
判断当前线程的中断标志位是否设置,调用后不清楚标志位。 对于以上方法设置标志位,Thread 收到通的方式有两种:
- 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,至于要不要结束线程或者有其它操作,则取决于 catch 中的代码内容,如 break 跳出循环结束线程。
- 否则,只是内部的一个中断标志被设置为 true。
public class Demo7 {public static void main(String[] args) {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()){System.out.println("hello thread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}}});t.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}t.interrupt();System.out.println("hello main!");} }
2.5 等待线程
有时我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。通过 join 方法,就能够起到阻塞线程的作用,从而达到手动控制线程执行顺序的一种办法(join 是控制结束顺序)。
方法 | 说明 |
---|---|
public void join()
|
等待线程结束 |
public void join(long millis)
|
等待线程结束,最多等待 millis 毫秒 |
public void join(long millis, int nanos)
|
等待线程结束,最多等待 millis 毫秒,精度更高 |
注意:在 main 线程中调用 t.join() 方法,是让 main 线程来等待 t 线程执行完毕。
应用:使用 Scanner 进行输入操作时,就运用了阻塞等待。
2.6 休眠线程
通过 sleep 方法就能够使调用 sleep 方法的线程进入阻塞等待的状态,至于阻塞的时间则取决于 sleep 方法中设置的时间参数。
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException
|
休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException
|
休眠当前线程 millis 毫秒,精度更高 |
注意:因为线程的调度是不可控的,所以这个方法只能够保证实际休眠时间是大于等于参数设置的休眠时间的。
一个进程中有多个线程,每个线程对应一个 PCB,即一个进程对应一组 PCB。操作系统调度 PCB 的时候,会从就绪队列中挑出一个 PCB 去 CPU 上执行,而处于阻塞队列中的 PCB 是不会被系统调度到 CPU 上执行的。而调用了 sleep 方法后,该线程就会被移动到阻塞队列中,只有到达了 sleep 的睡眠时间,该 PCB 才会被移动回就绪队列,但此时并不意味着该线程就能够在 CPU 上执行了,因为线程调度是不可控的。
3. 线程的状态
3.1 线程的所有状态
线程的状态是一个枚举类型 Thread.State,具体状态如下:
状态 | 说明 | 补充 |
---|---|---|
NEW | 安排了工作,还未开始行动 | Thread 创建出了对象,但是内核里面的线程还没创建,即未调用 start 方法。 |
RUNNABLE | 可以工作的,又可以分成正在工作中和即将开始工作 | 就绪状态 |
BLOCKED | 表示排队等着其它事情 | 阻塞状态,等待锁的时候产生的状态。 |
WAITING | 表示排队等着其它事情 | 阻塞状态,通过 wait 方法产生的状态。 |
TIMED_WAITING | 表示排队等着其它事情 | 阻塞状态,通过 sleep 方法产生的状态。 |
TERMINATED | 工作完成了 | 内核里的线程已经结束了,然后 Thread 创建的对象还在。 |
通过下面这段代码,就能够将 Java 线程的所有状态打印出来:
public class Demo8 {public static void main(String[] args) {for(Thread.State state: Thread.State.values()){System.out.println(state);}}
}
3.2 线程状态转移图
|
【多线程】多线程基础知识相关推荐
- java多线程学习笔记--一.多线程的基础知识
需要学习的知识 多线程基础知识讲解 参考索隆和jim的视频,以及自己做的笔记 导读 为了充分利用CPU资源,人们发明了线程和进程 进程 由来:在单核cpu的时期,为了方便操作把一系列的操作的指令写下来 ...
- auto.js停止所有线程_Java多线程编程基础知识 概念介绍,以及线程状态
一.进程 进程是操作系统结构的基础:是一次程序的执行:是一个程序及其数据在处理机上顺序执行时所发生的活动.操作系统中,几乎所有运行中的任务对应一条进程(Process).一个程序进入内存运行,即变成一 ...
- 给我十分钟带你过完java多线程所有基础知识
目录: 1.并发并行与线程进程 2.认识CPU和主线程 3.多线程的三种创建方式 4.三种创建多线程方式的进一步探究和对比 5.匿名内部类的多线程创建 6.多线程内存的分析 7.深度了解线程run() ...
- 初识多线程之基础知识与常用方法
1.线程与进程的描述: 1.1进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1~n个线程.(进程是资源分配的最小单位) 1.2线程:同一类线程共享代码和 ...
- python web框架 多线程_Django基础知识 web框架的本质详解
姑娘,你好,我是Jaden,请问约吗?嘻嘻~~ alert('这是我们第一个网页')
- java基础知识 多线程
package org.base.practise9; import org.junit.Test; import java.awt.event.WindowAdapter; import java. ...
- 【嵌入式Linux】嵌入式Linux应用开发基础知识之多线程编程
文章目录 前言 1.多线程基础编程--创建线程和使用等待函数休眠线程 1.1.程序分析--使用信号量PV操作sem_wait 1.2.程序分析--使用条件变量pthread_cond_wait 2.一 ...
- Java基础知识(十) 多线程
Java基础知识 1. 什么是多线程?它与进程有什么区别?为什么要使用多线程 2. 同步和异步有什么区别 3. 如何实现Java多线程 4. run()方法与start()方法有什么区别 5. 多线程 ...
- Java多线程基础知识
多线程基础知识 这是我再次学习多线程知识的一个总结,对于刚刚接触的学习者是比较友好易懂的,便于快速的理解和掌握. 一.基本概念: 1.进程:进程就是运行中的程序,当一个程序开始执行,操作系统就会给这个 ...
- JavaSE基础二十:Java 多线程(线程基础知识、Java 多线程、Java 实现多线程(继承 Thread 类、实现 Runnable 接口、实现 Callable 接口))
本章目录 1.基础知识准备 2.Java 多线程概述 3.Java 实现多线程 3.1.继承 Thread 类 如何开启新线程 Thread 类常用方法 多线程中的同步 Thread 类同步方法 多线 ...
最新文章
- 网格弹簧质点系统模拟(Spring-Mass System by Verlet Integration)附源码
- android图库文件夹乱,防止文件夹包含在Android图库中 | MOS86
- 干货 | 一文掌握常用的机器学习模型
- 这个时代,开发简单多了
- Sequence of methods in form and table in AX
- php pdo setfetchmode,PDOStatement::setFetchMode
- 编译OpenJDK12:链接freelib时提示 LNK4044,无法识别的选项
- 关于身份证校验算法的一些想法
- 让你一目了然的商业计划书
- 《神经网络与深度学习》编程笔记
- Ubuntu 18.04及Snap体验——让Linux入门更简单(转))
- 一个数据分析报告的框架,主要包含哪几项?
- 中标麒麟——初次体验,感觉流畅
- 1字节不是一定是8位。
- 设置goland里的行间距
- Python入门练习选择题
- 灵魂有香气的女子李筱懿:自律,是追求更高级的快乐
- android实现大文件断点上传
- Centos7+Open***使用用户及密码验证登陆
- dede模板里常用到的一些标签—dedecms模板开发
热门文章
- limbo运行veket linux,Veket——『350M』的操作系统,五脏俱全全到可怕!
- UCI数据集和源代码数据挖掘的数据集资源
- C语言猴子吃桃问题。猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时..
- python元组和列表逆序_Python容器:列表与元组
- 棒棒的二维数据可视化分类模型
- 银行计考试-计算机考点2-计算机系统组成与基本工作原理
- 公网访问本地内网web服务器【内网穿透】
- 矩阵的LU分解,LU分解的推广,LU分解有什么意义,为什么要用LU分解。
- Linux sed实战
- centos7查看oracle监听端口,CentOS 7开放及查看防火墙firewall的端口