IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【Java高并发核心编程(卷2)】第一章:多线程原理与实战 —— 进程和线程、线程的创建、操作、原理、线程池、ThreadLocal -> 正文阅读

[Java知识库]【Java高并发核心编程(卷2)】第一章:多线程原理与实战 —— 进程和线程、线程的创建、操作、原理、线程池、ThreadLocal

文章要配合源码看

进程和线程

进程的查看可以通过任务管理器查看

什么是进程

简单来说,进程是程序的一次启动执行。
程序是存放在硬盘中的可执行文件,主要包括代码指令和数据。
一个进程是一个程序的一次启动和执行,是操作系统将程序装入内存,给程序分配必要的系统资源,并且开始运行程序的指令。

进程的基本原理

进程的大致结构:
进程的大致结构

程序段 一般也被称为代码段。代码段是进程的程序指令在内存中的位置,包含需要执行的指令集合;

数据段 是进程的操作数据在内存中的位置,包含需要操作的数据集合;

程序控制块(ProgramControl Block,PCB)包含进程的描述信息和控制信息,是进程存在的唯一标志。

PCB主要由四大部分组成:

进程的描述信息。主要包括:进程ID和进程名称,进程ID是唯一的,代表进程的身份;进程状态,比如运行、就绪、阻塞;进程优先级,是进程调度的重要依据。

进程的调度信息。主要包括:程序起始地址,程序的第一行指令的内存地址,从这里开始程序的执行;通信信息,进程间通信时的消息队列。

进程的资源信息。主要包括:内存信息,内存占用情况和内存管理所用的数据结构;I/O设备信息,所用的I/O设备编号及相应的数据结构;文件句柄,所打开文件的信息。

进程上下文。主要包括执行时各种CPU寄存器的值、当前程序计数器(PC)的值以及各种栈的值等,即进程的环境。在操作系统切换进程时,当前进程被迫让出CPU,当前进程的上下文就保存在PCB结构中,供下次恢复运行时使用。

Java编写的程序都运行在Java虚拟机(JVM)中,每当使用Java命令启动一个Java应用程序时,就会启动一个JVM进程。在这个JVM进程内部,所有Java程序代码都是以线程来运行的。JVM找到程序的入口点main()方法,然后运行main()方法,这样就产生了一个线程,这个线程被称为主线程。当main()方法结束后,主线程运行完成,JVM进程也随即退出。

线程的基本原理

线程是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小单位。

线程是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小单位。

线程是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小单位。
在这里插入图片描述
在线程的结构中,线程描述信息即线程的基本信息,主要包括:
(1)线程ID(Thread ID,线程标识符)。线程的唯一标识,同一个进程内不同线程的ID不会重叠。
(2)线程名称。主要是方便用户识别,用户可以指定线程的名字,如果没有指定,系统就会自动分配一个名称。
(3)线程优先级。表示线程调度的优先级,优先级越高,获得CPU的执行机会就越大。
(4)线程状态。表示当前线程的执行状态,为新建、就绪、运行、阻塞、结束等状态中的一种。
(5)其他。例如是否为守护线程等,后面会详细介绍。在线程的结构中,程序计数器很重要,它记录着线程下一条指令的代码段内存地址。

程序计数器记录着线程下一条指令的代码段内存地址。

进程与线程的区别

(1)线程是“进程代码段”的一次顺序执行流程。一个进程由一个或多个线程组成,一个进程至少有一个线程。
(2)线程是CPU调度的最小单位,进程是操作系统分配资源的最小单位。线程的划分尺度小于进程,使得多线程程序的并发性高。
(3)线程是出于高并发的调度诉求从进程内部演进而来的。线程的出现既充分发挥了CPU的计算性能,又弥补了进程调度过于笨重的问题。
(4)进程之间是相互独立的,但进程内部的各个线程之间并不完全独立。各个线程之间共享进程的方法区内存、堆内存、系统资源(文件句柄、系统信号等)。
(5)切换速度不同:线程上下文切换比进程上下文切换要快得多。所以,有的时候,线程也称为轻量级进程。

创建线程的四种方法

Thread类简介

Thread具有id、name、priority、deamon、threadStatus、启动和运行、获取线程 等属性和方法。

简述几个常用的

priority

public final void setPriority(intpriority),设置线程优先级。Java线程的最大优先级值为10,最小值为1,默认值为5。

deamon

守护线程,是在进程运行时提供某种后台服务的线程,比如垃圾回收(GC)线程。

threadStatus

public Thread.State getState(),返回表示当前线程的执行状态,Thread的内部静态枚举类State用于定义Java线程的所有状态,具体如下:NEW(新建),RUNNABLE(就绪、运行),BLOCKED(阻塞),WAITING(等待),TIMED_WAITING(计时等待),TERMINATED(结束)

Java线程状态中,就绪状态和运行状态在内部都用RUNNABLE。就绪状态表示线程具备运行条件,正在等待获取CPU时间片;运行状态表示线程已经获取了CPU时间片,CPU正在执行线程代码逻辑。

start 和 run

public void start()

用来启动一个线程,当调用start()方法后,JVM才会开启一个新的线程来执行用户定义的线程代码逻辑,在这个过程中会为相应的线程分配需要的资源。

public void run()

作为线程代码逻辑的入口方法。run()方法不是由用户程序来调用的,当调用start()方法启动一个线程之后,只要线程获得了CPU执行时间,便进入run()方法体去执行具体的用户线程代码。

创建线程

创建一个空线程

public class EmptyThreadDemo
{
    public static void main(String args[]) throws InterruptedException
    {
        //使用Thread类创建和启动线程
        Thread thread = new Thread();
        Print.cfo("线程名称:" + thread.getName());
        Print.cfo("线程ID:" + thread.getId());
        Print.cfo("线程状态:" + thread.getState());
        Print.cfo("线程优先级:" + thread.getPriority());
        thread.start();
        Print.cfo("线程状态:" + thread.getState());
        ThreadUtil.sleepMilliSeconds(10);
    }
}

[EmptyThreadDemo.main]:线程名称:Thread-0
[EmptyThreadDemo.main]:线程ID:19
[EmptyThreadDemo.main]:线程状态:NEW
[EmptyThreadDemo.main]:线程优先级:5
[EmptyThreadDemo.main]:线程状态:RUNNABLE

thread调用start()方法,新线程会去调用run()方法,注意thread中的run()方法:

    public void run() {
        if (target != null) {
            target.run();
        }
    }

target是Thread类的一个实例属性,默认为空。在这个例子中,thread线程的target属性默认为null。所以在thread线程执行时,其run()方法其实什么也没有做,线程就执行完了。

继承Thread类

(1)需要继承Thread类,创建一个新的线程类。
(2)同时重写run()方法,将需要并发执行的业务代码编写在run()方法中。

public class CreateDemo {

    public static final int MAX_TURN = 5;
    static int threadNo = 1;

    static class DemoThread extends Thread {
        public DemoThread() {
            super("Mall-" + threadNo++);
        }

        public void run() {
            Print.cfo(getName() + ", 运行");
        }
    }

    public static void main(String args[]) throws InterruptedException {
        //方法一:使用Thread子类创建和启动线程
        new DemoThread().start();
        new DemoThread().start();
        Print.cfo(getCurThreadName() + " 运行结束.");
    }
}

实现Runnable接口

Thread类中的run方法

package java.lang;
public class Thread implements Runnable {
	...
	private Runnable target; // 执行目标
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    public Thread(Runnable target) {
    	// 包含执行目标的构造器
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
}
public class CreateDemo2 {
    public static final int MAX_TURN = 3;
    static int threadNo = 1;
    static class RunTarget implements Runnable  //① 实现Runnable接口
    {
        public void run()  //② 在这些写业务逻辑
        {
            for (int j = 1; j < MAX_TURN; j++) Print.cfo(getCurThreadName() + ", 轮次:" + j);
            Print.cfo(getCurThreadName() + " 运行结束.");
        }
    }
    public static void main(String args[]) throws InterruptedException {
        for (int i = 0; i < 2; i++) {
            Runnable target = new RunTarget();
            new Thread(target, "RunnableThread" + threadNo++).start();
        }
    }
}

[CreateDemo2$RunTarget.run]:RunnableThread1, 轮次:1
[CreateDemo2$RunTarget.run]:RunnableThread2, 轮次:1
[CreateDemo2$RunTarget.run]:RunnableThread1, 轮次:2
[CreateDemo2$RunTarget.run]:RunnableThread2, 轮次:2
[CreateDemo2$RunTarget.run]:RunnableThread2 运行结束.
[CreateDemo2$RunTarget.run]:RunnableThread1 运行结束.

值得注意的是,run()方法实现版本中在获取当前线程的名称时,所用的方法是在外部类ThreadUtil中定义的getCurThreadName()静态方法,而不是Thread类的getName()实例方法。原因是:这个RunTarget内部类和Thread类不再是继承关系,无法直接调用Thread类的任何实例方法。

通过实现Runnable接口的方式创建的执行目标类,如果需要访问线程的任何属性和方法,必须通过Thread.currentThread()获取当前的线程对象,通过当前线程对象间接访问。

通过继承Thread类的方式创建的线程类,可以在子类中直接调用Thread父类的方法访问当前线程的名称、状态等信息。这也是使用Runnable实现异步执行与继承Thread方法实现异步执行不同的地方。

实现Runnable接口创建线程目标类的优点

(1)可以避免由于Java单继承带来的局限性。如果异步逻辑所在类已经继承了一个基类,就没有办法再继承Thread类。

(2)逻辑和数据更好分离。通过实现Runnable接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。在同一个资源被多个线程逻辑异步、并行处理的场景中,通过实现Runnable接口的方式设计多个target执行目标类可以更加方便、清晰地将执行逻辑和数据存储分离,更好地体现了面向对象的设计思想。

继承Thread类或者实现Runnable接口这两种方式来创建线程类,但是这两种方式有一个共同的缺陷:不能获取异步执行的结果。

使用Callable和FutureTask创建线程

在这里插入图片描述

使用Callable创建线程
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Callable接口是一个泛型接口,也是一个“函数式接口”。其唯一的抽象方法call()有返回值,返回值的类型为Callable接口的泛型形参类型。call()抽象方法还有一个Exception的异常声明,容许方法的实现版本的内部异常直接抛出,并且可以不予捕获。
Callable实例能否和Runnable实例不一样,不能作为Thread线程实例的target来使用呢。Thread的target属性的类型为Runnable,而Callable接口与Runnable接口之间没有任何继承关系,并且二者唯一的方法在名字上也不同。RunnableFuture接口在Callable接口与Thread线程之间起到搭桥作用。

RunnableFuture接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

RunnableFuture继承了Runnable接口,从而保证了其实例可以作为Thread线程实例的target目标;同时,RunnableFuture通过继承Future接口,保证了可以获取未来的异步执行结果。

Future接口

Future接口至少提供了三大功能:
(1)能够取消异步执行中的任务。
(2)判断异步任务是否执行完成。
(3)获取异步任务完成后的执行结果。

public interface Future<V> {
	// 取消异步任务的执行。
    boolean cancel(boolean mayInterruptIfRunning);

	// 获取异步任务的取消状态。如果任务完成前被取消,就返回true。
    boolean isCancelled();

	// 获取异步任务的执行状态。如果任务执行结束,就返回true。
    boolean isDone();

	// 阻塞性获取异步任务执行的结果。
    V get() throws InterruptedException, ExecutionException;

	// 设置时限,(调用线程)阻塞性地获取异步任务执行的结果。
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask类

FutureTask类实现了RunnableFuture接口。RunnableFuture实现了Future接口和Runnable接口。FutureTask既能作为一个Runnable类型的target执行目标直接被Thread执行,又能作为Future异步任务来获取Callable的计算结果。

在FutureTask类的run()方法完成callable成员的call()方法的执行之后,其结果将被保存在 outcome 实例属性中,供FutureTask类的get()方法获取。

通过线程池创建
// 创建线程池
private static ExecutorService pool = Executors.newFixedThreadPool(3);
	//方法一:执行一个 Runnable类型的target执行目标实例,无返回
    void execute(Runnable command);
     
    //方法二:提交一个 Callable类型的target执行目标实例, 返回一个Future异步任务实例
    <T> Future<T> submit(Callable<T> task);  
                         
    //方法三:提交一个 Runnable类型的target执行目标实例, 返回一个Future异步任务实例
    Future<?> submit(Runnable task);

execute(…)与submit(…)方法的区别如下:

(1)接收的参数不一样

submit()可以接收两种入参:1、无返回值的Runnable类型的target执行目标实例,2、有返回值的Callable类型的target执行目标实例。

execute()仅仅接收无返回值的target执行目标实例,或者无返回值的Thread实例。

(2)submit()有返回值,而execute()没有

submit()方法在提交异步target执行目标之后会返回Future异步任务实例,以便对target的异步执行过程进行控制,比如取消执行、获取结果等。

execute()没有任何返回,target执行目标实例在执行之后没有办法对其异步执行过程进行控制,只能任其执行结束。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 12:48:23  更:2022-03-06 12:52:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:51:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码