java高并发系列-第1天:必须知道的几个概念

news2024/11/28 12:38:26

同步(Synchronous)和异步(Asynchronous)

同步和异步通常来形容一次方法调用,同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中“真实”地执行。整个过程,不会阻碍调用者的工作。

如图:

图片

上图中显示了同步方法调用和异步方法调用的区别。对于调用者来说,异步调用似乎是一瞬间就完成的。如果异步调用需要返回结果,那么当这个异步调用真实完成时,则会通知调用者。

打个比方,比如购物,如果你去商场买空调,当你到了商场看重了一款空调,你就向售货员下单。售货员去仓库帮你调配物品。这天你热的是在不行了,就催着商家赶紧给你送货,于是你就在商店里面候着他们,直到商家把你和空调一起送回家,一次愉快的购物就结束了。这就是同步调用。

不过,如果我们赶时髦,就坐在家里打开电脑,在电脑上订购了一台空调。当你完成网上支付的时候,对你来说购物过程已经结束了。虽然空调还没有送到家,但是你的任务已经完成了。商家接到你的订单后,就会加紧安平送货,当然这一切已经跟你无关了。你已经支付完成,想干什么就能去干什么,出去溜几圈都不成问题,等送货上门的时候,接到商家的电话,回家一趟签收就完事了。这就是异步调用。

并发(Concurrency)和并行(Parallelism)

并发和并行是两个非常容易被混淆的概念。他们都可以表示两个或者多个任务一起执行,但是侧重点有所不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的,而并行是真正意义上的“同时执行”,下图很好地诠释了这点。

图片

大家排队在一个咖啡机上接咖啡,交替执行,是并发;两台咖啡机上面接咖啡,是并行。

从严格意义上来说,并行的多任务是真的同时执行,而对于并发来说,这个过程只是交替的,一会执行任务A,一会执行任务B,系统会不停地在两者之间切换。但对于外部观察者来说,即使多个任务之间是串行并发的,也会造成多任务间并行执行的错觉。

并发说的是在一个时间段内,多件事情在这个时间段内交替执行

并行说的是多件事情在同一个时刻同事发生。

实际上,如果系统内只有一个CPU,而使用多进程或者多线程任务,那么真实环境中这些任务不可能是真实并行的,毕竟一个CPU一次只能执行一条指令,在这种情况下多进程或者多线程就是并发的,而不是并行的(操作系统会不停地切换多任务)。真实的并行也只可能出现在拥有多个CPU的系统中(比如多核CPU)。

临界区

临界区用来表示一种公共资源或者说共享数据,可以被多个线程使用,但是每一次只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源就必须等待。

比如,一个办公室里有一台打印机,打印机一次只能执行一个任务。如果小王和小明同时需要打印文件,很明显,如果小王先发了打印任务,打印机就开始打印小王的文件,小明的任务就只能等待小王打印结束后才能打印,这里的打印机就是一个临界区的例子。

在并行程序中,临界区资源是保护的对象,如果意外出现打印机同时执行两个任务的情况,那么最有可能的结果就是打印出来的文件是损坏的文件,它既不是小王想要的,也不是小明想要的。

阻塞(Blocking)和非阻塞(Non-Blocking)

阻塞和非阻塞通常用来形容很多线程间的相互影响。比如一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中等待。等待会导致线程挂起,这种情况就是阻塞。此时,如果占用资源的线程一直不愿意释放资源,那么其他线程阻塞在这个临界区上的线程都不能工作。

非阻塞的意思与之相反,它强调没有一个线程可以妨碍其他线程执行,所有的线程都会尝试不断向前执行。

死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)

死锁饥饿活锁都属于多线程的活跃性问题。如果发现上述几种情况,那么相关线程就不再活跃,也就是说它可能很难再继续往下执行了。

死锁应该是最糟糕的一种情况了(当然,其他几种情况也好不到哪里去),如下图显示了一个死锁的发生:

图片

A、B、C、D四辆小车都在这种情况下都无法继续行驶了。他们彼此之间相互占用了其他车辆的车道,如果大家都不愿意释放自己的车道,那么这个状况将永远持续下去,谁都不可能通过,死锁是一个很严重的并且应该避免和实时小心的问题,后面的文章中会做更详细的讨论。

饥饿是指某一个或者多个线程因为种种原因无法获得所要的资源,导致一直无法执行。比如它的优先级可能太低,而高优先级的线程不断抢占它需要的资源,导致低优先级线程无法工作。在自然界中,母鸡给雏鸟喂食很容易出现这种情况:由于雏鸟很多,食物有限,雏鸟之间的事务竞争可能非常厉害,经常抢不到事务的雏鸟有可能被饿死。线程的饥饿非常类似这种情况。此外,某一个线程一直占着关键资源不放,导致其他需要这个资源的线程无法正常执行,这种情况也是饥饿的一种。于死锁想必,饥饿还是有可能在未来一段时间内解决的(比如,高优先级的线程已经完成任务,不再疯狂执行)。

活锁是一种非常有趣的情况。不知道大家是否遇到过这么一种场景,当你要做电梯下楼时,电梯到了,门开了,这是你正准备出去。但很不巧的是,门外一个人当着你的去路,他想进来。于是,你很礼貌地靠左走,礼让对方。同时,对方也非常礼貌的靠右走,希望礼让你。结果,你们俩就又撞上了。于是乎,你们都意识到了问题,希望尽快避让对方,你立即向右边走,同时,他立即向左边走。结果,又撞上了!不过介于人类的智慧,我相信这个动作重复两三次后,你应该可以顺利解决这个问题。因为这个时候,大家都会本能地对视,进行交流,保证这种情况不再发生。但如果这种情况发生在两个线程之间可能就不那么幸运了。如果线程智力不够。且都秉承着“谦让”的原则,主动将资源释放给他人使用,那么久会导致资源不断地在两个线程间跳动,而没有一个线程可以同时拿到所有资源正常执行。这种情况就是活锁。

死锁的例子

 
  1. package com.jvm.visualvm;

  2. /**

  3. * <a href="http://www.itsoku.com/archives">Java干货铺子,只生产干货,公众号:javacode2018</a>

  4. */

  5. public class Demo4 {

  6. public static void main(String[] args) {

  7. Obj1 obj1 = new Obj1();

  8. Obj2 obj2 = new Obj2();

  9. Thread thread1 = new Thread(new SynAddRunalbe(obj1, obj2, 1, 2, true));

  10. thread1.setName("thread1");

  11. thread1.start();

  12. Thread thread2 = new Thread(new SynAddRunalbe(obj1, obj2, 2, 1, false));

  13. thread2.setName("thread2");

  14. thread2.start();

  15. }

  16. /**

  17. * 线程死锁等待演示

  18. */

  19. public static class SynAddRunalbe implements Runnable {

  20. Obj1 obj1;

  21. Obj2 obj2;

  22. int a, b;

  23. boolean flag;

  24. public SynAddRunalbe(Obj1 obj1, Obj2 obj2, int a, int b, boolean flag) {

  25. this.obj1 = obj1;

  26. this.obj2 = obj2;

  27. this.a = a;

  28. this.b = b;

  29. this.flag = flag;

  30. }

  31. @Override

  32. public void run() {

  33. try {

  34. if (flag) {

  35. synchronized (obj1) {

  36. Thread.sleep(100);

  37. synchronized (obj2) {

  38. System.out.println(a + b);

  39. }

  40. }

  41. } else {

  42. synchronized (obj2) {

  43. Thread.sleep(100);

  44. synchronized (obj1) {

  45. System.out.println(a + b);

  46. }

  47. }

  48. }

  49. } catch (InterruptedException e) {

  50. e.printStackTrace();

  51. }

  52. }

  53. }

  54. public static class Obj1 {

  55. }

  56. public static class Obj2 {

  57. }

  58. }

运行上面代码,可以通过jstack查看到死锁信息:

 
  1. "thread2" #13 prio=5 os_prio=0 tid=0x0000000029225000 nid=0x3c94 waiting for monitor entry [0x0000000029c9f000]

  2. java.lang.Thread.State: BLOCKED (on object monitor)

  3. at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:50)

  4. - waiting to lock <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1)

  5. - locked <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2)

  6. at java.lang.Thread.run(Thread.java:745)

  7. Locked ownable synchronizers:

  8. - None

  9. "thread1" #12 prio=5 os_prio=0 tid=0x0000000029224800 nid=0x6874 waiting for monitor entry [0x0000000029b9f000]

  10. java.lang.Thread.State: BLOCKED (on object monitor)

  11. at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:43)

  12. - waiting to lock <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2)

  13. - locked <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1)

  14. at java.lang.Thread.run(Thread.java:745)

  15. Locked ownable synchronizers:

  16. - None

thread1持有com.jvm.visualvm.Demo4$Obj1的锁,等待获取com.jvm.visualvm.Demo4$Obj2的锁 thread2持有com.jvm.visualvm.Demo4$Obj2的锁,等待获取com.jvm.visualvm.Demo4$Obj1的锁,两个线程相互等待获取对方持有的锁,出现死锁。

饥饿死锁的例子

 
  1. package com.jvm.jconsole;

  2. import java.util.concurrent.*;

  3. /**

  4. * <a href="http://www.itsoku.com/archives">Java干货铺子,只生产干货,公众号:javacode2018</a>

  5. */

  6. public class ExecutorLock {

  7. private static ExecutorService single = Executors.newSingleThreadExecutor();

  8. public static class AnotherCallable implements Callable<String> {

  9. @Override

  10. public String call() throws Exception {

  11. System.out.println("in AnotherCallable");

  12. return "annother success";

  13. }

  14. }

  15. public static class MyCallable implements Callable<String> {

  16. @Override

  17. public String call() throws Exception {

  18. System.out.println("in MyCallable");

  19. Future<String> submit = single.submit(new AnotherCallable());

  20. return "success:" + submit.get();

  21. }

  22. }

  23. public static void main(String[] args) throws ExecutionException, InterruptedException {

  24. MyCallable task = new MyCallable();

  25. Future<String> submit = single.submit(task);

  26. System.out.println(submit.get());

  27. System.out.println("over");

  28. single.shutdown();

  29. }

  30. }

执行代码,输出:

 
  1. in MyCallable

使用jstack命令查看线程堆栈信息:

 
  1. "pool-1-thread-1" #12 prio=5 os_prio=0 tid=0x0000000028e3d000 nid=0x58a4 waiting on condition [0x00000000297ff000]

  2. java.lang.Thread.State: WAITING (parking)

  3. at sun.misc.Unsafe.park(Native Method)

  4. - parking to wait for <0x0000000717921bf0> (a java.util.concurrent.FutureTask)

  5. at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)

  6. at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)

  7. at java.util.concurrent.FutureTask.get(FutureTask.java:191)

  8. at com.jvm.jconsole.ExecutorLock$MyCallable.call(ExecutorLock.java:25)

  9. at com.jvm.jconsole.ExecutorLock$MyCallable.call(ExecutorLock.java:20)

  10. at java.util.concurrent.FutureTask.run(FutureTask.java:266)

  11. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

  12. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

  13. at java.lang.Thread.run(Thread.java:745)

  14. Locked ownable synchronizers:

  15. - <0x00000007173f2690> (a java.util.concurrent.ThreadPoolExecutor$Worker)

  16. "main" #1 prio=5 os_prio=0 tid=0x00000000033e4000 nid=0x5f94 waiting on condition [0x00000000031fe000]

  17. java.lang.Thread.State: WAITING (parking)

  18. at sun.misc.Unsafe.park(Native Method)

  19. - parking to wait for <0x00000007173f1d48> (a java.util.concurrent.FutureTask)

  20. at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)

  21. at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)

  22. at java.util.concurrent.FutureTask.get(FutureTask.java:191)

  23. at com.jvm.jconsole.ExecutorLock.main(ExecutorLock.java:32)

  24. Locked ownable synchronizers:

  25. - None

图片

堆栈信息结合图中的代码,可以看出主线程在32行处于等待中,线程池中的工作线程在25行处于等待中,等待获取结果。由于线程池是一个线程,AnotherCallable得不到执行,而被饿死,最终导致了程序死锁的现象。

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

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

相关文章

下班后赚钱的8个副业,适合上班族

每个人都有不同的经济压力&#xff0c;尤其对于上班族来说&#xff0c;薪水或许不足以满足生活的各种需求和零花钱。因此&#xff0c;越来越多的人开始寻找机会在下班后赚取额外的收入。 如今有许多适合上班族的副业选择&#xff0c;帮助他们实现财务上的增长。不仅可以满足日常…

在Docker中设置Redis的密码

目录 1&#xff0c;介绍2&#xff0c;实现“Docker Redis设置密码”的整体流程3&#xff0c;具体实现步骤4&#xff0c;结论 1&#xff0c;介绍 Docker是一个开源的应用容器引擎&#xff0c;可以自动化部署、扩展应用程序。它可以帮助开发人员将应用程序及其依赖项打包到一个可…

中国社科院大学-新加坡新跃社科大学全球战略领导力博士学位教育项目招生简章

Singapore University of Social Sciences--University of Chinese Academy of Social Sciences Doctoral program on Global Strategic Leadership V13146152701 一、项目简介 全球经济正在经历由科技进步和创新、政治和人口剧烈变化所带来的巨大的不确定性和挑战。面对日…

Java中各个版本JDK分别有哪些常见的垃圾回收算法?它们的适用场景和开启方法是什么?

Java中各个版本JDK分别有哪些常见的垃圾回收算法&#xff1f;它们的使用场景和开启方法是什么&#xff1f; 1.1 JDK 1.8 中的垃圾回收算法1.2 JDK 11 中的垃圾回收算法1.3 JDK17中的垃圾回收算法 1.1 JDK 1.8 中的垃圾回收算法 Java 8引入了一些不同类型的垃圾回收算法&#x…

Vue中切换tab路由,提示this.$confirm确定和取消执行不同的逻辑

beforeRouteLeave (to, from, next) { // 离开页面 if (this.editFlag true) { this.$confirm(页面尚未保存&#xff0c;确认离开吗?, 提示, { distinguishCancelAndClose: true, // 区分取消和关闭 confirmButtonText: 确定, cancelButtonText: 取消, type: info }).then(()…

一文读懂RASP运行时防护平台及应用实践

「云原生安全既是一种全新安全理念&#xff0c;也是实现云战略的前提。 基于蚂蚁集团内部多年实践&#xff0c;云原生PaaS平台SOFAStack发布完整的软件供应链安全产品及解决方案&#xff0c;包括静态代码扫描Pinpoint&#xff0c;软件成分分析SCA&#xff0c;交互式安全测试IAS…

Git 安全警告修复手册:解决 `fatal: detected dubious ownership in repository at ` 问题 ️

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

打开运行ps提示找不到msvcp140.dll的解决方法分享

最近&#xff0c;我在安装Adobe Photoshop&#xff08;简称PS&#xff09;和Adobe Premiere Pro&#xff08;简称PR&#xff09;时遇到了一个困扰我很久的问题&#xff0c;即提示找不到msvcp140.dll文件。msvcp140.dll是Microsoft Visual C 2015 Redistributable的一个组件&…

MyBatis缓存详解

1. MyBatis缓存 MyBatis中的缓存是用来提高性能&#xff0c;减少数据库交互次数的机制。它分为一级缓存&#xff08;Local Cache&#xff09;和二级缓存&#xff08;Global Cache&#xff09;。 1.1 一级缓存&#xff08;Local Cache&#xff09; 作用范围&#xff1a;一级缓…

SpringBoot整合Kafka (一)

&#x1f4d1;前言 本文主要讲了SpringBoot整合Kafka文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句&#xff1a;…

数据库备份和恢复

备份&#xff1a; 完全备份 增量备份 完全备份&#xff1a;将整个数据库完整的进行备份。 增量备份&#xff1a;在完全备份的基础之上&#xff0c;对后续新增的内容进行备份。 备份的需求&#xff1a; 备份的需求&#xff1a; 1、在生产环境中&#xff0c;数据的安全至关重…

计算领域高质量科技期刊分级目录

最后需要文件的小伙伴可以私信我哦&#xff0c;也可以自行在官网下载

汽车网络安全渗透测试概述

目录 1.汽车网络安全法规概述 1.1 国外标准 1.2 国内标准 2.汽车网络安全威胁分析 2.1 汽车网络安全资产定义 2.2 汽车网络安全影响场景及评级示例 3.汽车网络安全渗透测试描述 3.1 参考法规 3.2 渗透测试内容 4.小结 1.汽车网络安全法规概述 近年来&#xff0c;汽车…

UE5——源码阅读——100——渲染——高清截图

创建事件&#xff0c;用于代码的调试 获取当前客户端所属的World 标记是否在进行重入绘制 是否开始缓存区可视化转存帧&#xff0c;主要针对请求屏幕截图或电影转存 判断是否需要高清截图 这下面这个函数执行高清截图 是否需要缓存区的可视化转存 判断是否开始渲染 如果…

总结Kibana DevTools如何操作elasticsearch的常用语句

一、操作es的工具 ElasticSearch HeadKibana DevToolsElasticHQ 本文主要是总结Kibana DevTools操作es的语句。 二、搜索文档 1、根据ID查询单个记录 GET /course_idx/_doc/course:202、term 匹配"name"字段的值为"6789999"的文档 类似于sql语句中的等…

自查看看自己转本复习中存在哪些问题

缺乏清晰、强力的奋斗目标 “我想考本科”只不过是一种笼统的泛化的模糊的专转本目标&#xff0c;对潜意识学习潜能的刺激力度不大。 专转本备考中&#xff0c;更需要一个恒久、量化、清晰、明确、具体的目标牵引自我潜意识去努力&#xff0c;克服学习中的重重困难。比如一所…

软件测试丨从外包到自研再到大厂——我是怎么从6k到25k的

功能测试&#xff1a;理论上说&#xff0c;该定位的测试人员应该是对业务需求理解最透彻的群体&#xff0c;专注于用户角度的测试&#xff0c;组织整体质量实践&#xff0c;分析测试运行结果&#xff0c;驱动测试执行。当然除了业务技能过硬&#xff0c;常用的测试工具也是必须…

VMware安装CentOS最小化开发环境导引

目录 一、概要 二、介绍 三、下载 四、安装 4.1 创建虚拟机 4.2 安装CentOS 五、配置网卡 六、配置本地安装源 七、安装软件 7.1 gcc/g 7.2 C的atomic库 7.3 java 7.4 Cmake 7.5 MariaDB客户端&#xff08;兼容mysql&#xff09; 八、用户配置文件.bash_profile…

Python小试牛刀:GUI(图形界面)实现计算器UI界面(三)

上一篇&#xff1a;Python小试牛刀&#xff1a;GUI&#xff08;图形界面&#xff09;实现计算器UI界面(二)-CSDN博客 回顾前两篇文章&#xff0c;第一篇文章主要实现了计算器UI界面如何布局&#xff0c;以及简单概述Python常用的GUI库。第二篇文章主要实现了计算器UI界面按钮组…

shiro-cve2016-4437漏洞复现

一、漏洞特征 Apache Shiro是一款开源强大且易用的Java安全框架&#xff0c;提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用&#xff0c;同时也能提供健壮的安全性。 因为在反序列化时,不会对其进行过滤,所以如果传入恶意代码将会造成安全问题 在 1.2.4 版本前, 加…