一、什么时候应该使用多线程?

今天看到一个问题,突然有感而发,想聊下这个话题。

不知道大家有没有想过这个问题,就是什么时候我该使用多线程呢?使用多线程就一定会提升系统性能吗?

1、其实是否应该使用多线程在很大程度上取决于应用程序的类型。

  • 计算密集型(如纯数学运算) 的, 并受CPU 功能的制约, 则只有多CPU(或者多个内核) 机器能够从更多的线程中受益, 单CPU下, 多线程不会带来任何性能上的提升, 反而有可能由于线程切换等额外开销而导致性能下降

  • IO密集型的,当我的应用必须等待缓慢的资源(如网络连接或者数据库连接上返回数据)时,那么多线程会让系统的CPU充分的利用起来,当一个线程阻塞挂起时,另一个线程可以继续使用CPU资源。

  • 其实,就是多线程不会增加CPU的处理能,而是能够更加充分地利用CPU资源。

由于同一进程的多个线程是共享同一片内存资源的,在带来方便的同时也必然会增加其复杂性,如何保证多线程访问数据的一致性问题等。而多线程属于编程中容易翻车的地方。并且多线程编程问题的测试定位也是比较难的。总体来说,好的多线程是写出来,将多线程问题寄希望于测试中发现, 无疑是极度不可靠的。SO,努力的学习吧。

Java API  与多线程息息相关的的几大关键字:volatile、synchronized、 wait、 notify. 理解了这几个关键字,就可以编写多线程的代码了。

二、什么时候需要加锁?

在多线程场合下,最重要的就是保障数据的一致性问题,而保障数据一致性问题,就需要借助于锁了。

其实我们在多线程的场景下应该搞清楚一个问题,就是到底什么需要保护?并不是所有的的数据都需要加锁保护,只有那些涉及到被多线程访问的共享的数据才需要加锁保护。

锁的本质其实就是确保在同一时刻,只有一个线程在访问共享数据,那么此时该共享数据就能得到有效的保护。

举例说明下,比如我们想构造一个多线程下安全的单向链表:


假如现在有两个线程在操作这个链表,一个写线程插入一个新元素7,另一个读线程遍历链表数据,如果不使用任何锁,那就有可能出现下面的执行顺序:

步骤 写线程 读线程
0 修改链表元素2的next指针指向7这个元素 ... ...
1 ... ... 遍历链表,到7的时候发现next 为null,遍历结束
2 修改元素7的next 指针指向3 ... ...

通过上面的例子我们可以明显看到在多线程下操作这个链表,有可能会导致读线程读到的数据不完整,只有从链表头部到元素7的位置的数据。由此可见,不加入任何保护措施的多线程保护,势必会导致数据的混乱。为了避免数据一致性问题,我们就需要将操作该队列的代码放入同步块内(锁的对象也就是这个链表实例),来确保同一时刻只有一个线程可以访问该链表。

如何加锁?

这里简单的说下,一般我们都是使用synchronized(如果没有特殊需求建议直接使用这个关键字,jdk新版本它真的很快),记住synchronized 锁的就是对象头。

简单的说下,主要有下面几种用法:

  • synchronized 放在方法上,锁的是当前synchronized 方法的对象实例

  • synchronized在static 方法上,锁的是synchronized 方法的class 类对象,注意这里class 其实也是一个对象。

  • synchronized(this)在代码块中,锁的是代码块括号内的对象,这里this指的就是调用这个方法的类实例对象

三、 多线程中易犯的错误

1、锁范围过大

共享资源访问完成后, 后续的代码没有放在synchronized同步代码块之外。会导致当前线程长期无效的占用该锁, 而其它争用该锁的线程只能等待, 最终导致性能受到极大影响。

 public void test(){   synchronized(lock){   ... ... //正在访问共享资源   ... ... //做其它耗时操作,但这些耗时操作与共享资源无关  } }

面对上面这种写法,会导致此线程长期占有此锁,从而导致其他线程只能等待,下面来讨论下解决方法:

1)单CPU场景下,将不需要同步的耗时操作拿到同步块外面,有的情况可以提升性能,有的却不行。

  • CPU密集型的代码 ,不存在磁盘IO/网络IO等低CPU消耗的代码。这种情况下, CPU 99%都在执行代码。因此缩小同步块也不会带来任何性能上的提升, 同时缩小同步块也不会带来性能上的下降。

  • IO密集型的代码,在执行不消耗CPU的代码时,其实CPU属于空闲状态的。如果此时让CPU工作起来就可以带来整体上性能的提升。所以在这种情况下,就可以将不需要同步的耗时操作移到同步块外面了。

2)多CPU场景下,将耗时的CPU操作拿到同步块外面,总是可以提升性能的

  • CPU密集型的代码,不存在IO操作等不消耗CPU的代码片段。因为当前是多CPU,其他CPU也可能是空闲的。所以在缩小同步块的时候,也会让其他线程尽快的执行这段代码从而带来性能上的提升。

  • IO密集型的代码,因为当前PCU都是空闲的状态,所以将耗时的操作放在同步块外面,一定会带来整体上的性能提升。

当然,不管怎么样,缩小锁的同步范围对于系统来说都是百利而无一害的,因此上面的代码应该改为:

 public void test(){   synchronized(lock){   ... ... //正在访问共享资源  }   ... ... //做其它耗时操作,但这些耗时操作与共享资源无关 }

综上所述,一个重点,就是只将访问共享资源的代码放在同步块内,保证快进快出。

2、死锁的问题

死锁要知道的:

  • 死锁,简单地说就是两个线程或多个线程在同时等待被对方持有的锁导致的,死锁会导致线程无法继续执行并被永久挂起。

  • 如果线程发生了死锁,那我们就能从线程堆栈中明显的看到”Found one Java-level deadlock“,并且线程栈还会给出死锁的分析结果。

  • 死锁这种问题如果发生在关键系统上就可能会导致系统瘫痪,如果想要快速恢复系统,临时唯一的方法就是保留线程栈先重启,然后再尽快的恢复。

  • 死锁这种问题有时候测试是很难被立即发现的,很多时候在测试时能否及时发现这类问题,就全看你的运气和你准备的测试用例了。

  • 避免死锁这类问题,唯一的办法就是改代码。但一个可靠的系统是设计出来的,而不是通过改BUG改出来的,当出现这种问题的时候就需要从系统设计角度去分析了。

  • 有人会认为死锁会导致CPU 100%,其实对也不对。要看使用的什么类型的锁了,比如synchronized导致的死锁,那就不会导致CPU100%,只会挂起线程。但如果是自旋锁这种才可能会消耗CPU。

3、共用一把锁的问题

就是多个共享变量会共用一把锁,特别是在方法级别上使用synchronized,从而人为导致的锁竞争。

上例子,下面是新手容易犯的错误:

1 public class MyTest2 {3 Object shared;4 synchronized void fun1() {...} //访问共享变量shared5 synchronized void fun2() {...} //访问共享变量shared6 synchronized void fun3() {...} //不访问共享变量shared7 synchronized void fun4() {...} //不访问共享变量shared8 synchronized void fun5() {...} //不访问共享变量shared9 }

上面的代码每一个方法都被加了synchronized ,明显违背了保护什么锁什么的原则。

三、线程数我们一般设多少比较合理呢?

其实大家都知道,在大多数场合下多线程都是可以提高系统的性能和吞吐量,但一个系统到底多少个线程才是合理的?

总的来说,线程数量太多太少其实都不太好,多了会因为线程频繁切换导致开销增大,有时候反而降低了系统性能。少了又会导致CPU资源不能充分的利用起来,性能没有达到瓶颈。

所以,系统到底使用多少线程合适,是要看系统的线程是否能充分的利用了CPU。其实实际情况,是很多时候不消耗CPU,如:磁盘IO、网络IO等。

磁盘IO、网络IO相比CPU的速度,那简直是相当的慢的,在执行IO的这段时间里CPU其实是空闲的。如果这时其他线程能把这空闲的CPU利用上,就可以达到提示系统性能和吞吐的目的。

其实上面我们也提到过,也就是两种计算特性:

CPU密集型:因为每个CPU都是高计算负载的情况,如果设置过多的线程反而会产生不必要的上下文切换。所以,一般线程我们会设置 CPU 核数 + 1就可以了,为啥要加1 呢,即使当计算(CPU)密集型的线程偶尔由于页缺失故障或者其他原因而暂停时,这个“额外”的线程也能确保 CPU 的时钟周期不会被浪费,其实就是个备份。

IO密集型:因为大量的IO操作,会导致CPU处于空闲状态,所以这时我们可以多设置些线程。所以, 线程数 = CPU 核心数 * (1+ IO 耗时/CPU 耗时) 就可以了,希望能给你点启发。

往期推荐

高并发系统,你需要知道的指标(RT...)

来,我们在重新说下,线程状态?

为什么微服务一定要有APM?看完这篇就懂了!

关于TCP 全连接队列的一些事情

ThreadDump分析实战(性能瓶颈分析)

pg多线程更新会发生死锁_何时用多线程?多线程需要加锁吗?线程数多少最合理?...相关推荐

  1. pg多线程更新会发生死锁_[C#.NET 拾遗补漏]12:死锁和活锁的发生及避免

    多线程编程时,如果涉及同时读写共享数据,就要格外小心.如果共享数据是独占资源,则要对共享数据的读写进行排它访问,最简单的方式就是加锁.锁也不能随便用,否则可能会造成死锁和活锁.本文将通过示例详细讲解死 ...

  2. c# 多线程 执行事件 并发_.NET异步和多线程系列(一)

    本系列将和大家分享.Net中的异步多线程,本文是该系列的开篇.首先来看些概念: 进程:计算机概念,虚拟的概念,程序在服务器运行时占据全部计算资源的总和,我们给它起个名字叫进程. 线程:计算机概念,虚拟 ...

  3. java多线程同步与死锁_浅析Java多线程中的同步和死锁

    Value Engineering 1基于Java的多线程 多线程是实现并发机制的一种有效手段,它允许编程语言在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间相互独立,且与进程一样拥有独立 ...

  4. 多线程读取同一个文件_前端进阶:多线程Web Workers的工作原理及使用场景

    Web Worker 概述 Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行.在主线程运行的同时,Worker ...

  5. python多线程实现生产者消费者_用Python实现多线程“生产者-消费者”模型的简单例子...

    用 Python 实现多线程"生产者 - 消费者"模型的简单例子 生产者消费者问题是一个著名的线程同步问题, 该问题描述如下: 有一个生产者在生产产品, 这些产品将提供给若干个消费 ...

  6. java 多线程并容器实现_跟着实例学习java多线程9-并发容器

    并发容器专门为并发而生的,最常用的就是ConcurrentHashMap.BlockingQueue了,这两个并发容器是我们比较常用的,前者取代同步Map提供了很好的并发性,后者提供了一种生产者与消费 ...

  7. c++ map 多线程同时更新值 崩溃_深入理解并发安全的 sync.Map

    golang中内置了map关键字,但是它是非线程安全的.从go 1.9开始,标准库加入了sync.Map,提供用于并发安全的map. 普通map的并发问题 map的并发读写代码 func main() ...

  8. c++ 多线程 类成员函数_多线程(C++/Python)

    多线程(C++/Python) 本文包括一下内容: 通过C++11的标准库进行多线程编程,包括线程的创建/退出,线程管理,线程之间的通信和资源管理,以及最常见的互斥锁,另外对python下多线程的实现 ...

  9. Java多线程中的死锁问题

    Java程序基本都要涉及到多线程,而在多线程环境中不可避免的要遇到线程死锁的问题.Java不像数据库那么能够检测到死锁,然后进行处理,Java中的死锁问题,只能通过程序员自己写代码时避免引入死锁的可能 ...

最新文章

  1. Java 组合模式及其应用
  2. Docker的安装和使用及其Docker容器间通信,云计算技术与应用实验报告
  3. 审计风险控制流程的起点_审计理论结构的起点和逻辑起点辨析
  4. golang TCP Socket编程
  5. python难嘛-为什么说python入门很简单,但是在你这很难?
  6. java jdbc脚本_关于java:使用MySQL和JDBC运行.sql脚本
  7. IntelliJ IDEA添加过滤文件或目录(转)
  8. scratch跳一跳游戏脚本_涂鸦骑士3D版强势屠榜,腾讯跳一跳“宝刀未老” | 休闲新游周报...
  9. 理想的互联网服务后台框架的九个要点
  10. Java 获取项目文件路径
  11. Linux 命令(19)—— tar 命令
  12. python基于pillow库的简单图像处理
  13. 机器学习算法(一): 基于逻辑回归的分类预测-Task01
  14. 关于H5页面的测试总结与分析
  15. 极速pdf android,极速PDF阅读器 V3.0.0.2003 官方版[安卓软件]
  16. 2021年了,对话系统凉透了吗?
  17. 浅析 HLS 流媒体协议
  18. 德语语法笔记——形容词综述
  19. jsp微信二维码收款_java实现微信支付之扫码支付
  20. 维特比算法的简单实现

热门文章

  1. 李飞飞:新技术变革时代的数据库产业
  2. 数据库自治服务DAS论文入选全球顶会SIGMOD,领航“数据库自动驾驶”新时代
  3. OceanBase首次阐述战略:继续坚持自研开放之路 开源300万行核心代码
  4. 给 COLA 做减法:应用架构中的“弯弯绕设计”
  5. 创意总监分享:我是如何做一款手游地图的
  6. 1000万粉丝女主播一年从未露脸,网友爆出嘴唇照,比腐团儿还好看
  7. MySQL锁阻塞分析
  8. linux利用grep查看打印匹配的下几行或前后几行的命令
  9. ORACLE 临时表空间使用率过高的原因及临时解决方案
  10. delete expired backup 和 delete obsolete