Java 高并发一:前言

所属分类: 软件编程 / java 阅读数: 40
收藏 0 赞 0 分享

1、关于高并发的几个重要概念

1.1 同步和异步

首先这里说的同步和异步是指函数/方法调用方面。

很明显,同步调用会等待方法的返回,异步调用会瞬间返回,但是异步调用瞬间返回并不代表你的任务就完成了,他会在后台起个线程继续进行任务。

1.2 并发和并行

并发和并行在外在表象来说,是差不多的。由图所示,并行则是两个任务同时进行,而并发呢,则是一会做一个任务一会又切换做另一个任务。所以单个cpu是不能做并行的,只能是并发。

1.3 临界区

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

1.4 阻塞和非阻塞

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

非阻塞允许多个线程同时进入临界区

所以阻塞的方式,一般性能不会太好。根据一般的统计,如果一个线程在操作系统层面被挂起,做了上下文切换了,通常情况需要8W个时间周期来做这个事情。

1.5 死锁、饥饿、活锁

所谓死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。就如同下图中的车都想前进,却谁都无法前进。

但是死锁虽说是不好的现象,但是它是一个静态的问题,一旦发生死锁,进程被卡死,cpu占有率也是0,它不会占用cpu,它会被调出去。相对来说还是比较好发现和分析的。

与死锁相对应的是活锁。

活锁,指事物1可以使用资源,但它让其他事物先使用资源;事物2可以使用资源,但它也让其他事物先使用资源,于是两者一直谦让,都无法使用资源。

举个例子,就如同你在街上遇到个人,刚好他朝着你的反方向走,与你正面碰到,你们都想让彼此过去。你往左边移,他也往左边移,两人还是无法过去。这时你往右边移,他也往右边移,如此循环下去。

一个线程在取得了一个资源时,发现其他线程也想到这个资源,因为没有得到所有的资源,为了避免死锁把自己持有的资源都放弃掉。如果另外一个线程也做了同样的事情,他们需要相同的资源,比如A持有a资源,B持有b资源,放弃了资源以后,A又获得了b资源,B又获得了a资源,如此反复,则发生了活锁。

活锁会比死锁更难发现,因为活锁是一个动态的过程。

饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。

1.6 并发级别

并发级别:阻塞和非阻塞(非阻塞分为无障碍、无锁、无等待)

1.6.1 阻塞

当一个线程进入临界区后,其他线程必须等待

1.6.2 无障碍

  1. 无障碍是一种最弱的非阻塞调度
  2. 自由出入临界区
  3. 无竞争时,有限步内完成操作
  4. 有竞争时,回滚数据

和非阻塞调度相比呢,阻塞调度是一种悲观的策略,它会认为说一起修改数据是很有可能把数据改坏的。而非阻塞调度呢,是一种乐观的策略,它认为大家修改数据未必把数据改坏。但是它是一种宽进严出的策略,当它发现一个进程在临界区内发生了数据竞争,产生了冲突,那么无障碍的调度方式则会回滚这条数据。

在这个无障碍的调度方式当中,所有的线程都相当于在拿去一个系统当前的一个快照。他们一直会尝试拿去的快照是有效的为止。

1.6.3 无锁

是无障碍的

保证有一个线程可以胜出

与无障碍相比,无障碍并不保证有竞争时一定能完成操作,因为如果它发现每次操作都会产生冲突,那它则会不停地尝试。如果临界区内的线程互相干扰,则会导致所有的线程会卡死在临界区,那么系统性能则会有很大的影响。

而无锁增加了一个新的条件,保证每次竞争有一个线程可以胜出,则解决了无障碍的问题。至少保证了所有线程都顺利执行下去。

下面代码是Java中典型的无锁计算代码

无锁在Java中很常见

while (!atomicVar.compareAndSet(localVar, localVar+1)) 
{ 
 localVar = atomicVar.get(); 
}

1.6.4 无等待

无锁的

要求所有的线程都必须在有限步内完成

无饥饿的

首先无等待的前提是无锁的基础上的,无锁它只保证了临界区肯定有进也有出,但是如果进的优先级都很高,那么临界区内的某些优先级低的线程可能发生饥饿,一直出不了临界区。那么无等待解决了这个问题,它保证所有的线程都必须在有限步内完成,自然是无饥饿的。

无等待是并行的最高级别,它能使这个系统达到最优状态。

无等待的典型案例:

如果只有读线程,没有线线程,那么这个则必然是无等待的。

如果既有读线程又有写线程,而每个写线程之前,都把数据拷贝一份副本,然后修改这个副本,而不是修改原始数据,因为修改副本,则没有冲突,那么这个修改的过程也是无等待的。最后需要做同步的只是将写完的数据覆盖原始数据。

由于无等待要求比较高,实现起来比较困难,所以无锁使用得会更加广泛一些。

2. 有关并行的两个重要定律

这两个定律都与加速比有关

2.1 Amdahl定律

定义了串行系统并行化后的加速比的计算公式和理论上限

加速比定义:加速比=优化前系统耗时/优化后系统耗时

举个例子:

加速比=优化前系统耗时/优化后系统耗时=500/400=1.25

这个定理表明:增加CPU处理器的数量并不一定能起到有效的作用 提高系统内可并行化的模块比重,合理增加并行处理器数量,才能以最小的投入,得到最大的加速比。

2.2 Gustafson定律

说明处理器个数,串行比例和加速比之间的关系

则加速比=n-F(n-1) //推导过程略

只要有足够的并行化,那么加速比和CPU个数成正比

更多精彩内容其他人还在看

Java的面向对象编程基本概念学习笔记整理

这篇文章主要介绍了Java的面向对象编程基本概念学习笔记整理,包括类与方法以及多态等支持面向对象语言中的重要特点,需要的朋友可以参考下
收藏 0 赞 0 分享

Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法

这篇文章主要介绍了Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法 的相关资料,需要的朋友可以参考下
收藏 0 赞 0 分享

基于Java实现杨辉三角 LeetCode Pascal's Triangle

这篇文章主要介绍了基于Java实现杨辉三角 LeetCode Pascal's Triangle的相关资料,需要的朋友可以参考下
收藏 0 赞 0 分享

Java中Spring获取bean方法小结

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,如何在程序中获取Spring配置的bean呢?下面通过本文给大家介绍Java中Spring获取bean方法小结,对spring获取bean方法相关知识感兴趣的朋友一起学习吧
收藏 0 赞 0 分享

如何计算Java对象占用了多少空间?

在Java中没有sizeof运算符,所以没办法知道一个对象到底占用了多大的空间,但是在分配对象的时候会有一些基本的规则,我们根据这些规则大致能判断出来对象大小,需要的朋友可以参考下
收藏 0 赞 0 分享

剖析Java中的事件处理与异常处理机制

这篇文章主要介绍了Java中的事件处理与异常处理机制,讲解Java是如何对事件或者异常作出响应以及定义异常的一些方法,需要的朋友可以参考下
收藏 0 赞 0 分享

详解Java的Struts2框架的结构及其数据转移方式

这篇文章主要介绍了详解Java的Struts2框架的结构及其数据转移方式,Struts框架是Java的SSH三大web开发框架之一,需要的朋友可以参考下
收藏 0 赞 0 分享

Java封装好的mail包发送电子邮件的类

本文给大家分享了2个java封装好的mail包发送电子邮件的类,并附上使用方法,小伙伴们可以根据自己的需求自由选择。
收藏 0 赞 0 分享

在Java的Struts中判断是否调用AJAX及用拦截器对其优化

这篇文章主要介绍了在Java的Struts中判断是否调用AJAX及用拦截器对其优化的方法,Struts框架是Java的SSH三大web开发框架之一,需要的朋友可以参考下
收藏 0 赞 0 分享

java多线程Future和Callable类示例分享

JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。今天我们就来研究下Future和Callab
收藏 0 赞 0 分享
查看更多