【JUC基础】01. 初步认识JUC

news2024/11/13 20:44:46

目录

1、前言

2、什么是JUC

3、并行和并发

4、进程和线程

5、如何创建子线程

5.1、继承Thread

5.2、实现Runnable

5.3、实现Callable

5.4、小结

6、Thread和Runnable

7、Runnable和Callable

8、线程状态

9、总结


1、前言

前段时间,有朋友跟我说,能否写一些关于JUC的教程文章。本来呢,JUC也有在我的专栏计划之内,只是一直都还没空轮到他,那么既然有这样的一个契机,那就把JUC计划提前吧。那么今天就重点来初步认识一下什么是JUC,以及一些基本的JUC相关基础知识。

关于JUC,建议配合Java API来学习(本文使用JAVA8)。Java API下载直达链接:https://download.csdn.net/download/p793049488/87743633

2、什么是JUC

JUC(java.util .concurrent),是JDK内置的一个用来处理并发(concurrent)的工具包。从JDK1.5开始就已经出现,该包中增加了很多使用在并发编程中常用的工具类和接口,包括线程池、原子类、锁、并发容器等。这些工具类和接口能够简化多线程编程的复杂度,提高程序的并发性能和可靠性。

其中包含了一些我们常见的工具类,如

  • Callable
  • ExecutorService
  • ThreadFactory
  • ConcurrentHashMap
  • ......

这些后面都会一一说到。

3、并行和并发

前面我们提到JUC是一个用来处理并发编程问题的工具包。那么什么是并发?相对于并发,很多时候人们更多听到的应该是并行,那么并行和并发有什么区别?

这里不得不请出我们的金牌教师C老师(ChatGPT)来给大家讲述一下:

简单总结一下就是:

  • 并行:同一时刻,任务同时进行。强调“同时”。
  • 并发:利用等待某些事情完成的时间,交替完成其他的事情。不一定是同时。更强调“交替”。

举个简单的例子:

假设你需要做一份午餐,你可以同时准备饭、菜、汤等多个食材,然后交替进行烹饪和加工,这就是并发。

如果你有一个烤箱和一个煤气灶,你可以同时在烤箱里烤面包,同时在煤气灶上煮汤,这就是并行。

4、进程和线程

  • 进程(process),是指操作系统中一个正在运行的程序的实例,它有自己的独立空间和资源,包括内存、文件、网络等。一个进程可以由一个或多个线程组成。如打开电脑的任务管理器,就能够看到每个正在运行的详情列表。

  • 线程(thread),是指操作系统中调度执行的最小单位,是进程中的一个执行单元。一个进程中的多个线程可以共享进程的资源,如内存、文件等。

以下是线程和进程的主要区别:

  1. 资源占用:一个进程占用独立的系统资源,包括内存、文件、网络等,而线程是在进程内运行的,多个线程可以共享进程的资源,减少资源占用。
  2. 切换开销:线程切换的开销要比进程小,因为线程是在进程内部调度的,而进程切换则需要保存和恢复进程的状态,这个过程比线程切换开销更大。
  3. 通信方式:在同一进程内的线程可以通过共享内存等方式进行通信,而不同进程之间的通信则需要使用进程间通信机制,如管道、消息队列等。
  4. 执行独立性:进程之间是独立的,一个进程的崩溃不会影响其他进程的执行,而线程之间共享进程的资源,一个线程的崩溃可能会导致整个进程崩溃。
  5. 系统开销:由于进程拥有自己独立的资源,进程间切换需要更多的系统开销,而线程共享进程的资源,切换开销更小。

总的来说,

进程是程序资源调度的基本单位。

线程是CPU执行的基本单位。

5、如何创建子线程

5.1、继承Thread

package com.github.fastdev;

public class Main {
    public static void main(String[] args) {
        new MyThread1("我是继承Thread的线程").start();
    }
}

class MyThread1 extends Thread {
    private String name;

    public MyThread1(String name) {
        this.name = name;
    }

    public void run() {
        System.out.println("Thread-1 " + name + " is running.");
    }
}

5.2、实现Runnable

package com.github.fastdev;

public class Main {
    public static void main(String[] args) {
        new Thread(new MyThread2("我是实现Runnable的线程")).start();
    }
}


class MyThread2 implements Runnable {
    private String name;

    public MyThread2(String name) {
        this.name = name;
    }

    public void run() {
        System.out.println("Thread-2 " + name + " is running.");
    }

}

5.3、实现Callable

package com.github.fastdev;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {

        // 由于new Thread构造函数无法接收callable。这里使用线程池的方式调用
        ExecutorService executor = Executors.newFixedThreadPool(1);
        executor.submit(new MyThread3("我是实现Callable的线程"));
    }
}

class MyThread3<String> implements Callable<String> {
    private String name;

    public MyThread3(String name) {
        this.name = name;
    }

    @Override    
    public String call() throws Exception {
        System.out.println("Thread-3 " + name + " is running.");
        return (String) "创建成功";
        
    }
}

5.4、小结

Thread中有两个方法,start()和run()。我们使用多线程并发时,应该使用start()方法,而不是run()方法。

start()用于启动一个线程,并在线程中执行run方法。一个线程只能start一次。

run()用于在本线程内执行,只是普通类的一个方法,可以被重复调用多次。如果在主线程中调用run(),那么就失去了并发的意义。

6、Thread和Runnable

从上面的代码中我们可以看出,要实现一个多线程编程。有以下几个步骤:

  1. 创建子线程,选择5.1-5.3三种方式之一创建即可。
  2. new Thread(),将执行线程传到Thread构造函数中。
  3. 调用start()方法。

那么既然Runnable或Callable已经能够创建出一个子线程,那么为什么还需要new Thread,调用它的start()呢?

通过查看Thread的源码可知,Thread本身其实是对Runnable的扩展:

 而Thread扩展了一系列线程的操作方法,如start(),stop(),yeild()......

而Runnable只是一个函数式接口而已,注意他只是个接口,而且他只有一个方法:run()。

而官方注释也明确告诉大家,Runnable应该由任何类来实现,该接口旨在为希望在活动状态下执行代码的对象提供公共协议。而大多数情况下,run()方法应该交由子类进行重写。

所以,Thread只是Runnable的一个实现,扩展了一系列方法操作线程的方法。我的理解是Runnable的存在,是为了更方便提供子类对线程操作的扩展。对于面向对象编程来说,这一类的扩展是很有必要的。网络上很多说“Runnable更容易可以实现多个线程间的资源共享,而Thread不可以”,这句话见仁见智,Runnable接口的存在,可以让你自由的定义很多可被重复使用的线程实现类,符合面向对象的思想。

7、Runnable和Callable

这个问题几乎是面试JUC基础中必问的一个题目。既然Runnable能够实现子线程的操作,也符合面向对象思想,那么为什么还需要Callable。而new Thread构造函数还不支持传入一个Callable,那Callable的意义在哪里呢?

答案就是:存在即合理。

先来看下Callable源码:

从源码可以看出Callable和Runnable的区别是:

  1. Runnable返回值是void,Callable返回值是一个泛型。
  2. Runnable的默认内置方法是run,Callable默认方法是call。
  3. Runnable默认没有抛异常,Callable有抛异常。

而事实证明确实如此,不仅源码这么说,官方文档也这么说:

当我们有需要某线程的执行状态,或需要对该线程的异常进行自定义处理,或需要获取多线程的反馈结果的时候。我们就需要用到Callable。

代码示例:

package com.github.fastdev;

import java.util.concurrent.*;
import java.lang.String;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Future<String> future = executor.submit(new MyThread3("我是实现Callable的线程"));
        System.out.println("线程返回结果:" + future.get());
    }
}


class MyThread3 implements Callable<java.lang.String> {
    private String name;

    public MyThread3(String name) {
        this.name = name;
    }

    @Override    
    public String call() throws Exception {
        System.out.println("Thread-3 " + name + " is running.");
        return "ok";
    }
}

返回结果:

8、线程状态

Java语言定义了6中线程状态。任意一个时间点,一个线程有且只有一种状态,并且可以通过特定方法切换不同状态。

  1. 新建(New):创建后尚未启用
  2. 运行(Runnable):包括了Running和Ready,此状态的线程由可能正在执行,也有可能正在等待操作系统分配执行时间
  3. 无限期等待(Waiting):该状态线程不会被分配执行时间,要等待被显式唤醒。以下几种情况下线程会处于该状态:
    1. 没设置Timeout参数的Object::wait()方法;
    2. 没设置Timeout参数的Object::join()方法;
    3. LockSupport::park()方法
  4. 限期等待(Timed Waiting):该状态线程不会被分配执行时间,不过无需等待被其他线程显式唤醒,在一定时间后由系统自动唤醒。以下几种情况下线程会处于该状态:
    1. Thread::sleep()方法。
    2. 设置了Timeout参数的Object::wait()方法。
    3. 设置了Timeout参数的Thread::join()方法。
    4. LockSupport::parkNanos()方法。
    5. LockSupport::parkUntil()方法。
  5. 阻塞(Blocked):线程被阻塞了。其中由阻塞状态和等待状态。
    1. 阻塞状态:在等待获取到一个排他锁,这个时间将在另一个线程放弃这个锁的时候发生;
    2. 等待状态:等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
  6. 结束(Terminated):种植线程状态,线程结束运行。

状态的转换关系如下图:

9、总结

自从多处理器问世以来,并发编程一直都是提高系统响应速率和吞吐率的最佳方式。但是相应也提高了编程的复杂度,相比单线程而言,多线程更加充满了未知性。一旦并发问题出现后,有时候没有特定的场景是根本无法复现的。因此我们更加需要巩固多线程的基础,才能从容应对多线程带来的一系列未知性问题。JUC基础学习第一篇就到这里吧,介绍一些常见的多线程知识为后面的学习铺垫。一天进步一点点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/480370.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(7) 支持向量机(上)

文章目录 1 概述1.1 支持向量机分类器是如何工作的 2 sklearn.svm.SVC2.1 线性SVM决策过程的可视化2.2 重要参数kernel&#xff08;核函数&#xff09;2.3 探索核函数在不同数据集上的表现2.4 探索核函数的优势和缺陷2.5 选取与核函数相关的参数&#xff1a;degree & gamma…

【Java笔试强训 27】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525; 不用加…

VSCode下载、安装和简单配置

之前提到Python IDE的选择时&#xff0c;VSCode以其轻便、简洁、高效、专业等优点成为最适合做Python工程项目开发的IDE&#xff0c;本期就来详细讲解一下VSCode的一个下载、安装以及Python开发环境的配置。 一、下载 直接进入VSCode官网&#xff0c;选择对应系统版本的VSCod…

切片、索引和排序

关于使用Series切片带尾片的疑惑。 切片是数字的时候不带尾片 切片非数字时带尾片 索引 可以使用loc()和iloc()选择数据。轴标签(loc())&#xff0c;整数标签(iloc())。 # 第一行列名为’A‘&#xff0c;’B‘的行。 print( df.loc[1, [A, B]])# [0, 1)的列为 [B(1), A(0…

默认成员函数:详解类的隐式操作

目录 一.类的默认成员函数二.构造函数三.析构函数。四.拷贝构造函数五.赋值运算符重载 一.类的默认成员函数 类的默认成员函数就是定义一个类后&#xff0c;类会自动生成的成员函数&#xff0c;若我们显示定义则类不会自动生成。 二.构造函数 在数据结构学习阶段我们手撕过栈…

VC++ | MFC应用程序设计:框架搭建

VC | MFC应用程序设计&#xff1a;框架搭建 时间&#xff1a;2023-05-01 文章目录 VC | MFC应用程序设计&#xff1a;框架搭建1.启动程序2.新建项目2-1.新建项目2-2.应用程序类型2-3.文档模板属性2-4.用户界面功能2-5.高级功能选项2-6.生成的类2-7.解决方案资源管理器 3.工程文…

如何在外远程控制我的世界服务器 - MCSM面板【端口映射】

文章目录 概述1.MCSManager 安装2.内网穿透2.1 安装cpolar内网穿透 3. 访问公网地址4.固定公网地址4.1 保留一个二级子域名4.2 配置固定二级域名4.3 访问固定公网地址 5. 设置节点公网地址6. 固定节点公网地址6.1 保留一个固定tcp地址6.2 配置固定TCP地址 转载自远程穿透文章&a…

【Latex】有关于Latex tabularray的一些很不错的教程、模板

1. 简介&#xff1a; 除了大家熟知的tabular&#xff0c;Latex在2021年出了一个table排版的新包&#xff1a;tabularray。 笔者这几天初步体验了一下tabularray&#xff0c;个人觉得tabularray明显比tabular的使用体感好不少。 不管是从排版的效果、便捷程度&#xff0c;还是…

基于NumPy构建LSTM模块并进行实例应用(附代码)

文章目录 0. 前言0.1 读本文前的必备知识 1. LSTM架构2. LSTM正向传播代码实现2.1 隐藏层正向传播2.2 输出层正向传播 3. LSTM反向传播代码实现3.1 输出层反向传播3.2 隐藏层反向传播 4. 实例应用说明5. 运行结果6. 后记6 完整代码 0. 前言 按照国际惯例&#xff0c;首先声明&a…

目标跟踪--卡尔曼滤波 与 匈牙利算法

目前主流的目标跟踪算法都是基于Tracking-by-Detecton策略&#xff0c;即基于目标检测的结果来进行目标跟踪。 跟踪结果中&#xff0c;每个bbox左上角的数字是用来标识某个人的唯一ID号。那么问题就来了&#xff0c;视频中不同时刻的同一个人&#xff0c;位置发生了变化&#x…

西瓜书读书笔记整理(三)—— 第二章 模型评估与选择

第二章 模型评估与选择 第 2 章 模型评估与选择2.1 经验误差与过拟合1. 错误率 / 精度 / 误差2. 训练误差 / 经验误差 / 泛化误差3. 过拟合 / 欠拟合4. 学习能力5. 模型选择 2.2 评估方法1. 评估方法概述2. 留出法3. 交叉验证法4. 自助法5. 调参 / 最终模型 2.3 性能度量1. 回归…

【JavaEE】UDP数据报套接字—实现回显服务器(网络编程)

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 本篇文章将带你了解什么是网络编程&#xff1f; 网络编程&#xff0c;指网络上的主机&#xff0c;通过不同的进程&#xff0c;以编程的方式实现网络通信&#xff08;或称为网络数据传输&am…

中断-STM32

中断-STM32 中断:在主程序运行过程中&#xff0c;出现了特定的中断触发条件 (中断源)&#xff0c;使得CPU暂停当前正在运行的程序转而去处理中断程序处理完成后又返回原来被暂停的位置继续运行。 中断优先级:当有多个中断源同时申请中断时&#xff0c;CPU会根据中断源的轻重缓…

Java程序猿搬砖笔记(十一)

文章目录 Hexo博客 Next主题图片防盗链问题Springboot Druid数据库密码加密配置步骤Java统计字符串出现的次数Java获取某个字符在字符串中出现第N次的位置Maven激活指定profileMaven中resources标签的用法详解MySQL 字符集不一致报错EasyExcel日期格式化Configuration、Compone…

gradle Task 详解

Task定义和配置 查看工程下所有的task&#xff0c;使用如下命令 gradle tasks 定义一个task task创建的源码 参数分别是 task 名称&#xff0c;和一个 closure。groovy语法的closure可以写在小括号外面&#xff0c;小括号可以省略 task的源码 public interface Task extends…

【Java笔试强训 25】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;星际密码…

RabbitMQ死信队列延迟交换机

RabbitMQ死信队列&延迟交换机 1.什么是死信 死信&死信队列 死信队列的应用&#xff1a; 基于死信队列在队列消息已满的情况下&#xff0c;消息也不会丢失实现延迟消费的效果。比如&#xff1a;下订单时&#xff0c;有15分钟的付款时间 2. 实现死信队列 2.1 准备E…

网络编程代码实例:IO复用版

文章目录 前言代码仓库内容代码&#xff08;有详细注释&#xff09;server.cclient_select.cclient_poll.cclient_epoll.c 结果总结参考资料作者的话 前言 网络编程代码实例&#xff1a;IO复用版。 代码仓库 yezhening/Environment-and-network-programming-examples: 环境和…

[Linux]网络连接、资源共享

​⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;Linux基础操作。本文主要是分享一些Linux系统常用操作&#xff0c;内容主要来源是学校作业&#xff0c;分享出来的…

详解c++---vector模拟实现

目录标题 准备工作构造函数迭代器的完善性质相关的函数实现reservepush_back[ ]emptyresizeinserteraseerase后迭代器失效问题swapclear~vector老式拷贝构造迭代器构造新式拷贝构造老式赋值重载新式赋值重载N个数据的构造vector的浅拷贝问题 准备工作 首先我们知道vector是一个…