Java并发编程基础总结

进程和线程概念

什么进程

进程是系统运行的基本单位,通俗的理解我们计算机启动的每一个应用程序都是一个进程。如下图所示,在Windows中这一个个exe文件,都是一个进程。而在JVM下,每一个启动的Main方法都可以看作一个进程。

在这里插入图片描述

什么是线程

线程是比进程更小的单位,所以在进行线程切换时的开销会远远小于进程,所以线程也常常被称为轻量级进程。每一个进程中都会有一个或者多个线程,在JVM中每一个Java线程都会共享他们的进程的堆区方法区。但是每一个进程都会有自己的程序计数器虚拟机栈本地方法栈

Java天生就是一个多线程的程序,我们完全可以运行下面这段代码看看一段main方法中会有那些线程在运行

public class MultiThread {public static void main(String[] args) {// 获取 Java 线程管理 MXBeanThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();// 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);// 遍历线程信息,仅打印线程 ID 和线程名称信息for (ThreadInfo threadInfo : threadInfos) {System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());}}
}

输出结果如下,所以Java程序在main函数运行时,还有其他的线程再跑。

[6] Monitor Ctrl-Break //这个线程是IDEA用来监控Ctrl-Break中断信号的线程
[5] Attach Listener //添加事件
[4] Signal Dispatcher // 方法处理Jvm信号的线程
[3] Finalizer //清除finalize 方法的线程
[2] Reference Handler // 清除引用的线程
[1] main // main入口

从JVM角度理解进程和线程的区别

图解两者区别

如下图所示,可以看出线程是比进程更小的单位,进程是独立的,彼此之间不会干扰,但是线程在同一个进程中共享堆区和方法区,虽然开销较小,但是资源之间管理和分配处理相对于进程之间要更加小心。

在这里插入图片描述

程序计数器、虚拟机栈、本地方法栈为什么线程中是各自独立的

  1. 程序计数器私有的原因:学过计算机组成原理的小伙伴应该都知晓,程序计数器用于记录当前下一条要执行的指令的单元地址,JVM也一样,有了程序计数器才能保证在多线程的情况下,这个线程被挂起再被恢复时,我们可以根据程序计数器找到下一次要执行的指令的位置。
  2. 虚拟机栈私有的原因:每一个Java线程在执行方法时,都会创建一个栈帧用于保存局部变量常量池引用操作数栈等信息,在这个方法调用到完成前,它对应的信息都会基于栈帧保存在虚拟机栈上。
  3. 本地方法栈私有的原因:和虚拟机栈类似,只不过本地方法栈保存的native方法的信息。

所以为了保证局部变量不被别的线程访问到,虚拟机栈和本地方法栈都是私有的,这就是我们解决某些线程安全问题时,常会用到一个叫栈封闭技术

关于栈封闭技术如下所示,将变量放在局部,每个线程都有自己的虚拟机栈,线程安全

public class StackConfinement implements Runnable {//全部变量 多线操作会有现场问题int globalVariable = 0;public void inThread() {//栈封闭技术,将变量放在局部,每个线程都有自己的虚拟机栈 线程安全int neverGoOut = 0;synchronized (this) {for (int i = 0; i < 10000; i++) {neverGoOut++;}}System.out.println("栈内保护的数字是线程安全的:" + neverGoOut);//栈内保护的数字是线程安全的:10000}@Overridepublic void run() {for (int i = 0; i < 10000; i++) {globalVariable++;}inThread();}public static void main(String[] args) throws InterruptedException {StackConfinement r1 = new StackConfinement();Thread thread1 = new Thread(r1);Thread thread2 = new Thread(r1);thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(r1.globalVariable); //13257}
}

多线程常见面试题

并发和并行的区别是什么?

  1. 并发:并发我们可以理解为,两个线程先后执行,但是从宏观角度来看,他们几乎是并行的。
  2. 并行:并行我们可以理解为两个线程同一时间都在运行。

同步和异步是什么意思?

  1. 同步:同步就是一个调用没有结果前,不会返回,直到有结果的才返回。
  2. 异步:异步即发起一个调用后,不等结果如何直接返回。

为什么需要多线程,多线程解决了什么问题

从宏观角度来看:线程可以理解为轻量级进程,切换开销远远小于进程,所以在多核CPU的计算机下,使用多线程可以更好的利用计算机资源从而提高计算机利用率和效率来应对现如今的高并发网络环境。

从微观场景下来说: 单核场景,在单核CPU情况下,假如一个线程需要进行IO才能执行业务逻辑,若只有单线程,这就意味着IO期间发生阻塞线程却只能干等。假如我们使用多线程的话,在当前线程IO期间,我们可以将其挂起,让出CPU时间片让其他线程工作。

多核场景下,假如我们有一个很复杂的任务需要进程各种IO和业务计算,假如只有一个线程的话,无论我们有多少个CPU核心,因为单线程的缘故他永远只能利用一个CPU核心,假如我们使用多线程,那么这些线程就会映射到不同的CPU核心上,做到最好的利用计算机资源,提高执行效率,执行事件约为单线程执行事件/CPU核心数。

创建线程方式有哪些(重点)

  1. 继承Thread 实现多线程
//继承Thread 然后start
public class Task extends Thread {public void run() {for (int x = 0; x < 60; x++)System.out.println("Task run----" + x);}public static void main(String[] args) throws InterruptedException {Task d = new Task();//创建好一个线程。d.start();//开启线程并执行该线程的run方法。d.join();}
}
  1. Runable接口实现多线程
//实现Runnable 方法
public class Ticket implements Runnable {private int tick = 100;public void run() {synchronized (Ticket.class) {while (true) {if (tick > 0) {System.out.println(Thread.currentThread().getName() + "....sale : " + tick--);}}}}
}public class Ticket implements Runnable {private int tick = 100;public void run() {synchronized (Ticket.class) {while (true) {if (tick > 0) {System.out.println(Thread.currentThread().getName() + "....sale : " + tick--);}else{break;}}}}
}
  1. FutureTask+Callable
FutureTask<String> futureTask=new FutureTask<>(()-> "123");new Thread(futureTask).start();try {System.out.println(futureTask.get());} catch (Exception e) {e.printStackTrace();} 

为什么需要Runnable接口实现多线程

由于Java的类只能单继承,当一个类已有继承类时,某个函数需要扩展为多线程这时候,Runnable接口就是最好的解决方案。

Thread和Runnable使用的区别

  1. 继承Thread:线程代码存放在Thread子类的run方法中,调用start()即可实现调用。
  2. Runnable:线程代码存在接口子类的run方法中,需要实例化一个线程对象Thread并将其作为参数传入,才能调用到run方法。

Thread类中run()和start()的区别

  1. run:仅仅是方法,在线程实例化之后使用run等于一个普通对象的直接调用。
  2. start:开启了线程并执行线程中的run方法,这期间程序才真正执行从用户态到内核态,创建线程的动作。

Java线程有哪几种状态(笔试)

新建(NEW):新创建的了一个线程对象,该对象并没有调用start()
可运行(RUNNABLE):线程对象创建后,并调用了start方法,等待分配CPU时间执行代码逻辑。
阻塞(BLOCKED):阻塞状态,等待锁的释放。当线程在synchronized 中被wait,然后再被唤醒时,若synchronized 有其他线程在执行,那么它就会进入BLOCKED状态。
等待(WAITING):因为某些原因被挂起,等待其他线程通知或者唤醒。
超时等待(TIME_WAITING):等待时间后自行返回,而不像WAITING那样没有通知就一直等待。
终止(TERMINATED):该线程执行完毕,终止状态了。

和操作系统的线程状态的区别

如下图所示,实际上操作系统层面可将RUNNABLE分为Running以及ReadyJava设计者之所以没有区分那么细是因为现代计算机执行效率非常高,这两个状态在宏观角度几乎无法感知。现代操作系统对多线程采用时间分片的抢占式调度算法,使得每个线程得到CPU10-20ms 处于运行状态,然后在让出CPU时间片,在不久后又会被调度执行,所以对于这种微观状态区别,Java设计者认为没有必要为了这么一瞬间进行这么多的状态划分。

在这里插入图片描述

什么是上下文切换

线程在执行过程中都会有自己的运行条件和状态,这些运行条件和状态我们就称之为线程上下文,这些信息例如程序计数器虚拟机栈本地方法栈等信息。当出现以下几种情况的时候就会从占用CPU状态中退出:

  1. 线程主动让出CPU,例如调用wait或者sleep等方法。
  2. 线程的CPU 时间片用完 而退出CPU占用状态 (因为操作系统为了避免某些线程独占CPU导致其他线程饥饿的情况就设定的例如时间分片算法)
  3. 线程调用了阻塞类型的系统中断,例如IO请求等。
  4. 线程被终止或者结束运行。

上述的前三种情况都会发生上下文切换。为了保证线程被切换在恢复时能够继续执行,所以上下文切换都需要保存线程当前执行的信息,并恢复下一个要执行线程的现场。这种操作就会占用CPU和内存资源,频繁的进行上下文切换就会导致整体效率低下。

线程死锁问题

如下图所示,两个线程各自持有一把锁,必须拿到对方手中那把锁才能释放自己的锁,正是这样一种双方僵持的状态就会导致线程死锁问题。

在这里插入图片描述

翻译称代码就如下图所示

public class DeadLockDemo {public static final Object lock1 = new Object();public static final Object lock2 = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (lock1){System.out.println("线程1获得锁1,准备获取锁2");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2){System.out.println("线程1获得锁2");}}}).start();new Thread(() -> {synchronized (lock2){System.out.println("线程2获得锁2,准备获取锁1");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock1){System.out.println("线程2获得锁1");}}}).start();}
}

输出结果

线程1获得锁1,准备获取锁2
线程2获得锁2,准备获取锁1

符合以下4个条件的场景就会发生死锁问题:

  1. 互斥:一个资源任意时间只能被一个线程获取。
  2. 请求与保持条件:一个线程拿到资源后,在获取其他资源而进入阻塞期间,不会释放已有资源。
  3. 不可剥夺条件:该资源被线程使用时,其他线程无法剥夺该线程使用权,除非这个线程主动释放。
  4. 循环等待条件:若干线程获取资源时,取锁的流程构成一个头尾相接的环,如上图。

预防死锁的3种方式

  1. 破坏请求与保持条件:以上面代码为例,我们要求所有线程必须一次性获得两个锁才能进行业务处理。即要求线程一次性获得所有资源才能进行逻辑处理。
  2. 破坏不可剥夺:资源被其他线程获取时,我们可以强行剥夺使用权。
  3. 破坏循环等待:这个就比较巧妙了,例如我们上面lock1 id为1,lock2id为2,我们让每个线程取锁时都按照lock的id顺序取锁,这样就避免构成循环队列。
  4. 操作系统思想(银行家算法):这个就涉及到操作系统知识了,大抵的意思是在取锁之前对资源分配进行评估,如果在给定资源情况下不能完成业务逻辑,那么就避免这个线程取锁,感兴趣的读者可以

sleep和wait方法区别

  1. sleep不会释放锁,只是单纯休眠一会。而wait则会释放锁。
  2. sleep单纯让线程休眠,在给定时间后就会苏醒,而wait若没有设定时间的话,只能通过notify或者notifyAll唤醒。
  3. sleepThread 的方法,而waitObject 的方法
  4. wait常用于线程之间的通信或者交互,而sleep单纯让线程让出执行权。

为什么sleep会定义在Thread

因为sleep要做的仅仅是让线程休眠,所以不涉及任何锁释放等逻辑,放在Thread上最合适。

为什么wait会定义在Object 上

我们都知道使用wait时就会释放锁,并让对象进入WAITING 状态,会涉及到资源释放等问题,所以我们需要将wait放在Object 类上。

可以直接调用 Thread 类的 run 方法吗?

若我们编写run方法,然后调用Threadstart方法,线程就会从用户态转内核态创建线程,并在获取CPU时间片的时候开始运行,然后运行run方法。
若直接调用run方法,那么该方法和普通方法没有任何差别,它仅仅是一个名字为run的普通方法。

假如在进程中, 已经开辟了多个线程, 其中一个线程怎么中断其它线程?

找到线程对应线程组即可定位到线程,然后调用interrupt将其打断即可。但如果想精确定位线程,我们还是建议使用ThreadLocal对线程做个标记。

ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();if (threadGroup != null) {Thread[] threads = new Thread[(int) (threadGroup.activeCount() * 1.2)];int count = threadGroup.enumerate(threads, true);for (int i = 0; i < count; i++) {if (threads[i].getId() == threadId) {return threads[i];}}}return null;

IO阻塞的线程会占用CPU资源吗?如何避免线程霸占CPU?

由于讲述问题的篇幅比较大,笔者专门写了一篇文章来讨论这两个问题,感兴趣的朋友可以看看:来聊聊IO阻塞与CPU任务调度

参考文章

Java并发编程:volatile关键字解析:https://www.cnblogs.com/dolphin0520/p/3920373.html

图解 | 你管这破玩意叫线程池?: https://mp.weixin.qq.com/s?__biz=Mzk0MjE3NDE0Ng==&mid=2247491549&idx=1&sn=1d5728754e8c06a621bbdca336d85452&chksm=c2c66570f5b1ec66df623e5300084257bd943b134d34e16abaacdb58834702dbbc4599868b89&scene=178&cur_album_id=1703494881072955395#rd

我是一个线程:https://mp.weixin.qq.com/s/IkNfuE541Mqqbv2iLIhMRQ

Java 并发常见面试题总结(上):https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html#什么是线程和进程

创建线程几种方式_线程创建的四种方式及其区别:https://cloud.tencent.com/developer/article/2135189#:~:text=创建线程的几种方式: 方式1:通过继承Thread类创建线程 步骤:1.定义Thread类的子类,并重写该类的run方法,该方法的方法体就是线程需要执行的任务,因此run,()方法也被称为线程执行体 2.创建Thread子类的实例,也就是创建了线程对象 3.启动线程,即调用线程的start ()方法

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

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

相关文章

如何用gpt改写文章 (1) 神码ai

大家好&#xff0c;今天来聊聊如何用gpt改写文章 (1)&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff1a; 如何用GPT改写文章 一、引言 随着人工智能技术的飞速发展&#xff0c;自然语言处理领域取得了重大突…

【数电笔记】54-或非门构成的基本RS触发器

目录 说明&#xff1a; 1. 电路组成 2. 逻辑功能 3. 特性表 4. 特性方程 5. 例题 6. 两种基本RS触发器的形式比 说明&#xff1a; 笔记配套视频来源&#xff1a;B站&#xff1b;本系列笔记并未记录所有章节&#xff0c;只对个人认为重要章节做了笔记&#xff1b;标题前…

02-详解请求路由的实现和常见的断言工厂

请求路由 路由转发 第一步: 新建一个SpringBoot工程如gateway模块, 引入网关依赖和nacos服务发现依赖 <!--网关依赖--> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId&…

玄关柜和鞋柜是一回事吗?福州中宅装饰,福州装修

玄关柜和鞋柜虽然都用于存放鞋子&#xff0c;但它们在概念上有所不同。玄关柜是一个更大的概念&#xff0c;它包括鞋柜和其他功能区域&#xff0c;可以说鞋柜是玄关柜的一部分。 1️⃣概念上的不同 玄关柜是一种集成了鞋柜、挂衣架、换鞋凳等多种功能于一体的家居家具&#xf…

Nginx(四层+七层代理)+Tomcat实现负载均衡、动静分离

一、Tomcat多实例部署 具体步骤请看我之前的博客 写文章-CSDN创作中心https://mp.csdn.net/mp_blog/creation/editor/134956765?spm1001.2014.3001.9457 1.1 访问测试多实例的部署 1.2 分别在三个tomcat服务上部署jsp的动态页面 mkdir /usr/local/tomcat/webapps/test vim …

Fork和Join底层原理

文章目录 一、任务类型1. 简介2. CPU密集型3. IO密集型4. 线程数计算方法 二、Fork/Join框架1. 思想2. Fork/Join简介3. Fork/Join使用4. 底层原理5. 总结 一、任务类型 1. 简介 思考: 线程池的线程数设置多少合适? 我们调整线程池中的线程数量的最主要的目的是为了充分并合理…

数字孪生轻量化引擎——AMRT3D引擎

随着全球经济亟待复苏&#xff0c;作为科技发展主要需求技术之一&#xff0c;数字孪生已经成为全球多个国家重点布局行业。例如&#xff0c;美国工业互联网盟将数字孪生作为工业互联网落地的核心和关键&#xff0c;德国工业4.0参考架构将数字孪生作为重要内容。 数字孪生已经形…

SSRF攻击实例讲解

服务器端请求伪造&#xff08;SSRF&#xff09;攻击是一种网络安全漏洞&#xff0c;其中攻击者迫使受影响的服务器向攻击者指定的内部或外部系统发送请求。以下是一个SSRF攻击的实例讲解及其分析。 SSRF攻击实例 当然&#xff0c;下面提供另外三个SSRF&#xff08;服务器端请…

2022最新云存储网盘系统,文件分享系统与文件存储系统。

资源入口 2022 最新云存储网盘系统, 文件分享系统与文件存储系统。 测试环境&#xff1a;Apache MySQL5.6 PHP7.0 安装 PHP 扩展 exif、fileinfo 从 PHP 禁用函数中 删除 shell_exec、proc_open、putenv 这三个 PHP 函数 PS&#xff1a;整体还不错的系统&#xff0c;注意的…

【学习笔记】V8垃圾回收策略

V8 V8是一款主流的JavaScript执行引擎V8采用即时编译,速度比较快V8内存设限,64位操作系统中上限为1.5G,32位系统中不超过800M V8垃圾回收策略 采用分代回收的思想内存分为新生代\老生代针对不同对象采用不同算法 v8常用的GC算法: 分代回收、空间复制、标记清除、标记整理、…

实战——Mac M2 安装mat工具

线上环境出现内存飙升的情况&#xff0c;需要工具定位问题发生点就需要用到mat工具了&#xff0c;之前都是在intel芯片环境上安装的&#xff0c;现在换了m2芯片&#xff0c;导致出现了问题&#xff0c;经过一系列调研都解决了&#xff0c;特此记录下&#xff0c;以备后查 开发…

语音压缩扩展器电路芯片TA31101/TA31101F——内含压缩和扩展器,噪声低 工作电压低电流小

TA31101/TA31101F是一块语音压缩扩展器电路&#xff0c;内含信号整流器单元、可变增益单元、运算放大器等电路单元&#xff0c;适用于无绳电话等产品中作语音压缩与扩展。 TA31101采用DIP 16的封装形式封装&#xff0c;TA31101F采用SOP 16的封装形式。 主要特点&#xff1a;●…

利用tf-idf对特征进行提取

TF-IDF是一种文本特征提取的方法&#xff0c;用于评估一个词在一组文档中的重要性。 一、代码 from sklearn.feature_extraction.text import TfidfVectorizer import numpy as npdef print_tfidf_words(documents):"""打印TF-IDF矩阵中每个文档中非零值对应…

社交网络分析1:起源发展、不同领域的应用、核心概念

社交网络分析1&#xff1a;社交网络相关定义和概念 写在最前面关于课程 社交网络、社交网络分析社交网络发展阶段&#xff08;自己感兴趣&#xff09;1. 社交网络的起源2. 社交网络的演变3. 社交网络的成熟4. 发展阶段补充和展望 2023社交大变革&#xff08;自己感兴趣的点&…

【Linux系统编程】初步运用git工具

介绍&#xff1a; 使用git之前首先要先认识gitee/github&#xff0c;gitee/github是一个远程仓库网站。git是平台专门开发的一个操控工具&#xff0c;是一个开源的分布式版本控制系统&#xff0c;我们使用git工具来与gitee/github来取得联系。 git的推送使用&#xff1a; git既…

商城免费搭建之java鸿鹄云商 java电子商务商城 Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c

鸿鹄云商 SAAS云产品概述 1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、My…

光纤的连接

光纤在工程布线中&#xff0c;难免会遇到线不够长或者磨损折断的情况&#xff0c;要怎么处理呢&#xff1f; 首先看看光纤的结构&#xff1a; 纤芯&#xff1a;中心部分&#xff0c;光波在纤芯中传输。 包层&#xff1a;环绕纤芯&#xff0c;折射率低于纤芯&#xff0c;作用是…

Talk | UCSB博士生王丹青: 大语言模型的协作学习以及个性化生成评估

本期为TechBeat人工智能社区第555期线上Talk。 北京时间12月13日(周三)20:00&#xff0c;加州大学圣塔芭芭拉分校博士生—王丹青的Talk已准时在TechBeat人工智能社区开播&#xff01; 她与大家分享的主题是: “大语言模型的协作学习以及个性化生成评估”&#xff0c;介绍了她的…

基于JAVAEE技术校园车辆管理系统论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本校园车辆管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息…

互联网加竞赛 python 机器视觉 车牌识别 - opencv 深度学习 机器学习

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于python 机器视觉 的车牌识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;3分 &#x1f9ff; 更多资…