精华内容
下载资源
问答
  • 实战并发编程 - 04基于不可变模式解决并发问题_2
    千次阅读
    2021-08-13 00:38:48

    在这里插入图片描述

    Pre

    实战并发编程 - 03基于不可变模式解决并发问题_1 中我们通过一个案例了解了可变类可能会导致什么样的线程安全问题,同时也学习了什么是不可变类,如何将一个可变类改造为一个不可变类。

    本篇文章则继续基于一个真实的“百万流量的短信网关系统”

    更多相关内容
  • JAVA如何解决并发问题

    千次阅读 2020-06-17 20:30:19
    并发问题的根源在哪 首先,我们要知道并发要解决的是什么问题?并发要解决的是单进程情况下硬件资源无法充分利用的问题。而造成这一问题的主要原因是CPU-内存-磁盘三者之间速度差异实在太大。如果将CPU的速度比作...

    并发问题的根源在哪

    首先,我们要知道并发要解决的是什么问题?并发要解决的是单进程情况下硬件资源无法充分利用的问题。而造成这一问题的主要原因是CPU-内存-磁盘三者之间速度差异实在太大。如果将CPU的速度比作火箭的速度,那么内存的速度就像火车,而最惨的磁盘,基本上就相当于人双腿走路。

    这样造成的一个问题,就是CPU快速执行完它的任务的时候,很长时间都会在等待磁盘或是内存的读写。

    计算机的发展有一部分就是如何重复利用资源,解决硬件资源之间效率的不平衡,而后就有了多进程,多线程的发展。并且演化出了各种为多进程(线程)服务的东西:

    • CPU增加缓存机制,平衡与内存的速度差异
    • 增加了多个概念,CPU时间片,程序计数器,线程切换等,用以更好得服务并发场景
    • 编译器的指令优化,希望在内部充分利用硬件资源

    但是这样一来,也会带来新的并发问题,归结起来主要有三个。

    • 由于缓存导致的可见性问题
    • 线程切换带来的原子性问题
    • 编译器优化带来的有序性问题

    我们分别介绍这几个:

    缓存导致的可见性

    CPU为了平衡与内存之间的性能差异,引入了CPU缓存,这样CPU执行指令修改数据的时候就可以批量直接读写CPU缓存的内存,一个阶段后再将数据写回到内存。

    但由于现在多核CPU技术的发展,各个线程可能运行在不同CPU核上面,每个CPU核各有各自的CPU缓存。前面说到对变量的修改通常都会先写入CPU缓存,再写回内存。这就会出现这样一种情况,线程1修改了变量A,但此时修改后的变量A只存储在CPU缓存中。这时候线程B去内存中读取变量A,依旧只读取到旧的值,这就是可见性问题。

    线程切换带来的原子性

    为了更充分得利用CPU,引入了CPU时间片时间片的概念。进程或线程通过争用CPU时间片,让CPU可以更加充分得利用。

    比如在进行读写磁盘等耗时高的任务时,就可以将宝贵的CPU资源让出来让其他线程去获取CPU并执行任务。

    但这样的切换也会导致问题,那就是会破坏线程某些任务的原子性。比如java中简单的一条语句count += 1。

    映射到CPU指令有三条,读取count变量指令,变量加1指令,变量写回指令。虽然在高级语言(java)看来它就是一条指令,但实际上确是三条CPU指令,并且这三条指令的原子性无法保证。也就是说,可能在执行到任意一条指令的时候被打断,CPU被其他线程抢占了。而这个期间变量值可能会被修改,这里就会引发数据不一致的情况了。所以高并发场景下,很多时候都会通过锁实现原子性。而这个问题也是很多并发问题的源头。

    编译器优化带来的有序性

    因为现在程序员编写的都是高级语言,编译器需要将用户的代码转成CPU可以执行的指令。

    同时,由于计算机领域的不断发展,编译器也越来越智能,它会自动对程序员编写的代码进行优化,而优化中就有可能出现实际执行代码顺序和编写的代码顺序不一样的情况。

    而这种破坏程序有序性的行为,在有些时候会出现一些非常微妙且难以察觉的并发编程bug。

    举个简单的例子,我们常见的单例模式是这样的:

    public class Singleton {
     
     private Singleton() {}
    
     private static Singleton sInstance;
    
     public static Singleton getInstance() {
    
      if (sInstance == null) {	//第一次验证是否为null
       synchronized (Singleton.class) {  //加锁
        if (sInstance == null) {	 //第二次验证是否为null
         sInstance = new Singleton(); //创建对象
             }
           }
         }
      return sInstance;
      }
    
    }

    即通过两段判断加锁来保证单例的成功生成,但在极小的概率下,可能会出现异常情况。原因就出现在sInstance = new Singleton();这一行代码上。这行代码,我们理解的执行顺序应该是这样:

    1. 为Singleton象分配一个内存空间。
    2. 在分配的内存空间实例化对象。
    3. 把Instance 引用地址指向内存空间。

    但在实际编译的过程中,编译器有可能会帮我们进行优化,优化完它的顺序可能变成如下:

    1. 为Singleton对象分配一个内存空间。
    2. 把instance 引用地址指向内存空间。
    3. 在分配的内存空间实例化对象。

    按照优化完的顺序,当并发访问的时候,可能会出现这样的情况

    1. A线程进入方法进行第1次instance == null判断。
    2. 此时A线程发现instance 为null 所以对Singleton.class加锁。
    3. 然后A线程进入方法进行第2次instance == null判断。
    4. 然后A线程发现instance 为null,开始进行对象实例化。
    5. 为对象分配一个内存空间。
    6. .把Instance 引用地址指向内存空间(而就在这个指令完成后,线程B进入了方法)。
    7. B线程首先进入方法进行第1次instance == null判断。B线程此时发现instance 不为null ,所以它会直接返回instance (而此时返回的instance 是A线程还没有初始化完成的对象)

    最终线程B拿到的instance 是一个没有实例化对象的空内存地址,所以导致instance使用的过程中造成程序错误。解决办法很简单,可以给sInstance对象加上一个关键字,volatile,这样编译器就不会乱优化,有关volatile的具体内容后续再细说。

    主要解决办法

    通过上面的介绍,其实可以归纳无论是CPU缓存,线程切换还是编译器优化乱序,出现问题的核心都是因为多个线程要并发读写某个变量或并发执行某段代码。那么我们可以控制,一次只让一个线程执行变量读写就可以了,这就是互斥。

    而在某些时候,互斥还不够,还需要一定的条件。比如一个生产者一个消费者并发,生产者向队列存东西,消费者向队列拿东西。那么生产者写的时候要保证存的时候队列不是满的,消费者要保证拿的时候队列非空。这种线程与线程间需要通信协作的情况,称为同步,同步可以说是更复杂的互斥。

    既然知道了并发编程的根源以及同步和互斥,那我们来看看有哪些解决的思路。其实一共也就三种:

    • 避免共享
    • Immutability(不变性)
    • 管程及其他工具

    下面我们分别说说这三种方案的优缺点

    避免共享

    我们先来说说避免共享,其实避免共享说是线程本地存储技术,在java中指的一般就是Threadlocal。ThreadLocal会为每个线程提供一个本地副本,每个线程都只会修改自己的ThreadLocal变量。这样一来就不会出现共享变量,也就不会出现冲突了。

    其实现原理是在ThreadLocal内部维护一个ThreadLocalMap,每次有线程要获取对应变量的时候,先获取当前线程,然后根据不同线程取不同的值,典型的以空间换时间。

    所以ThreadLocal还是比较适用于需要共享资源,且资源占用空间不大的情况。比如一些连接的session啊等等。但是这种模式应用场景也较为有限,比如需要同步情况就难以胜任。

    Immutability(不变性)

    Immutability在函数式中用得比较多,函数式编程的一个主要目的是要写出无副作用的代码,有关什么是无副作用可以参考我以前的文章Scala函数式编程指南(一) 函数式思想介绍。而无副作用的一个主要特点就是变量都是Immutability即不可变的,即创建对象后不会再修改对象,比如scala默认的变量和数据结构都是不可变的。而在java中,不变性变量即通过final修饰的变量,如String,Long,Double等类型都是Immutability的,它们的内部实现都是基于final关键字的。

    那这又和并发编程有什么关系呢?其实啊,并发问题很大部分原因就是因为线程切换破坏了原子性,这又导致线程随意对变量的读写破坏了数据的一致性。而不变性就不必担心这个问题,因为变量都是不变,不可写只能读的。在这种编程模式下,你要修改一个变量,那么只能新生成一个。这样做的好处很明显,但坏处也是显而易见,那就是引入了额外的编程复杂度,丧失了代码的可读性和易用性。

    因为如此,不变性的并发解决方案其实相对而已没那么广泛,其中比较有代表性的算是Actor并发编程模型,我以前也有讨论过,有兴趣可以看看Actor模型浅析 一致性和隔离性,这种编程模型和常规并发解决方案有很显著的差异。按我的了解,Acctor模式多用在分布式系统的一些协调功能,比如维持集群中多个机器的心跳通信等等。如果在单机并发环境下,还是下面要介绍的管程类工具才是利器。

    管程及其他工具

    其实最早的操作系统中,解决并发问题用的是信号量,信号量通过两个原子操作wait(S),和signal(S)(俗称P,V操作)来实现访问资源互斥和同步。比如下面这个小例子:

    //整型信号量定义
    int S;
    
    //P操作
    wait(S){
     while(S<=0);
     S--;
    }
    
    //V操作
    signal(S){
     S++;
    }

    虽然信号量方便有效,但信号量要对每个共享资源都实现对应的P和V操作,这使得并发编程中可能要出现大量的P,V操作,并且这部分内容难以抽象出来。

    为了更好地实现同步互斥,于是就产生了管程(即Monitor,也有翻译为监视器),值得一提的是,管程也有几种模型,分别是:Hasen模型,Hoare模型和MESA模型。其中MESA模型应用最广泛,java也是参考自MESA模型。这里简单介绍下管程的理论知识,这部分内容参考自进程同步机制-----为进程并发执行保驾护航,希望了解更多管程理论知识的童鞋可以看看。

    我们来通过一个经典的生产-消费队列来解释,如下图

    我们先解释下图中右半部分的内容,右上角有一个等待调用的线程队列,管程中每次只能有一个线程在执行任务,所以多个任务需要等待。然后是各个名词的意思,生产-消费需要往队列写入和取出东西,这里的队列就是共享变量,对共享资源进行操作称之为过程(入队和出队两个过程)。而向队列写入和取出是有条件的,写入的时候队列必须是非满的,取出的时候队列必须是非空的,这两个条件被称为条件变量

    然后再来看看左半部分的内容,假设线程T1读取共享变量(即队列),此时发现队列为空(条件变量之一),那么T1此时需要等待,去哪里等呢?去条件变量队列不能为空对应的队列中去等待。此时另一个线程T2向共享变量队列写数据,通过了条件变量队列不能满,那么写完后就会通知线程T1。但因为管程的限制,管程中只能有一个线程在执行,所以T1线程不能立即执行,它会回到右上角的线程等待队列等待(不同的管程模型在这里是有分歧的,比如Hasen模型是立即中断T2线程让队列中下一个线程执行)。

    解释完这个图,管程的概念也就呼之欲出了,

    hansen对管程的定义如下:一个管程定义了一个数据结构和能力为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。

    本质上,管程是对共享资源以及对共享资源的操作抽象成变量和方法,要操作共享变量仅能通过管程提供的方法(比如上面的入队和出队)间接访问。所以你会发现管程其实和面向对象的理念是十分相近的,在java中,主要提供了低层次了synchronized关键字和wait(),notify()等方法。同时还提供了高层次的ReenTrantLock和Condition来实现管程模型。

    以上~

    展开全文
  • 用存储过程解决并发问题

    千次阅读 2018-12-03 16:46:34
    需要解决:多人阅读,阅读量相应加1 1、mysql建立存储过程     二、xml编写  &lt;select id="IncreaseReading" statementType="CALLABLE"&gt;  call increase_reading(  #...

    需要解决:多人阅读,阅读量相应加1

    1、mysql建立存储过程

     

     

    二、xml编写

        <select id="IncreaseReading" statementType="CALLABLE">
              call increase_reading(  
                 #{nid,jdbcType=VARCHAR,mode=IN},
                 #{r_result,jdbcType=INTEGER,mode=OUT}
               )  
        </select>
        

     

    三、service

    /**修改
         * @param pd
         * @throws Exception
         */
        public void IncreaseReading(String NOTICE_ID)throws Exception{
          Map<String, Object> map = new HashMap<String, Object>();  
            map.put("nid",NOTICE_ID);  
            map.put("r_result ",null);  
            dao.findForObject("NoticeMapper.IncreaseReading", map);
        /*    int result = MapUtils.getInteger(map, "r_result", -3);*/
        }

     

    public void IncreaseReading(String NOTICE_ID)throws Exception;

     

    四、调用

        noticeService.IncreaseReading(1);

     

    展开全文
  • 主要介绍了MySQL 如何处理高并发,帮助大家更好的优化MySQL数据库,感兴趣的朋友可以了解下
  • 主要介绍了使用队列(Queue)解决简单的并发问题,讲解的很细致,喜欢的朋友们可以了解一下
  • 文章目录Pre解决并发问题的方法无锁的方式解决并发问题局部变量不可变对象ThreadLocalCAS原子类有锁的方式解决并发问题synchronized关键字ReentrantLock可重入锁 Pre 解决并发问题的方法 解决并发问题的方法分为两...
    展开全文
  • 主要介绍了Redis高并发问题解决办法,具有很好的参考价值,感兴趣的小伙伴们可以参考一下,具体如下:
  • 主要介绍了PHP+Redis链表解决并发下商品超卖问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
  • 场景: 类似银行存取,增减账户余额,...更新同一条记录可以通过增加version字段解决并发同时写的问题 但是明细表中先读取原先余额再写入新余额,这个怎么解决,怎么能让每次读出余额是最新的,写入明细之前余额不会被修改
  • java得用锁解决并发问题的简单示例

    千次阅读 2017-02-09 14:16:11
    java得用锁解决并发问题的简单示例
  • MySQL - 并发事务问题解决方案

    千次阅读 2020-08-10 20:49:47
    并发事务处理也会带来一些问题,如:脏读、不可重复读、幻读等等 脏读 一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加...
  • PHP解决并发问题的几种实现方法

    千次阅读 2018-03-07 10:11:50
    本文实例讲述了PHP开发中解决并发问题的几种实现方法。分享给大家供大家参考,具体如下:对于商品抢购等并发场景下,可能会出现超卖的现象,这时就需要解决并发所带来的这些问题了在PHP语言中并没有原生的提供并发的...
  • Redis incr解决并发问题

    万次阅读 2019-08-15 11:19:33
    2、每天的工单生成量是30W,所以会存在并发问题 解决思路: 1、首先乐观的认为redis不会宕机,对应的缓存不会被清除(除非人为操作,人为操作会有独立的补救办法) 2、将工单编码存到缓存中(redis),其值只存...
  • 这个Demo演示了使用redis乐观锁机制解决并发环境下出现连接超时与超卖(库存出现负数)情况的解决办法。
  • 解决redis高并发问题的几种思路

    千次阅读 2020-08-03 16:20:54
    解决redis高并发问题的几种思路 1:布隆过滤器 首先,布隆过滤器能解决绝大部分恶意攻击的请求,比如我们数据库中的id通常都设为自增的,是有一定范围大小的,如果有黑客恶意用数据库中没有的id一直访问我们的数据库...
  • 主要介绍了java解决并发数据重复问题 ,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 如何解决并发问题

    千次阅读 2019-04-01 20:44:34
    负载均衡将是大型网站解决高负荷访问和大量并发请求采用的终极解决办法。 (1)单个重负载的运算分担到多台节点设备上做并行处理,每个节点设备处理结束后,将结果汇总,返回给用户,系统处理能力得到大幅度提高. ...
  • 现在很多公司的招聘信息,都会有这这么一条要求:有分布式、高并发、高负载、高可用系统设计、开发和调优经验者优先。写这个岗位描述的HR,恨不得把自己知道的所有看上去高大上的词全都堆砌上,恨不...
  • 初识elasticsearch解决并发问题

    千次阅读 2018-08-15 19:09:54
    一、乐观锁和悲观锁 ①悲观锁:  顾名思义,就是很悲观,每次去拿数据的时候都认为被人会修改,所以每次拿数据的时候都会加锁,以防别人修改,直到操作完成后... 悲观锁的缺点:并发能力低,同一时间只能有一个...
  • 如何解决并发,秒杀问题

    万次阅读 多人点赞 2018-06-27 22:46:54
    相信不少人会被这个问题困扰,分享大家一篇这样的文章,希望能够帮到你!一、秒杀业务为什么难做?1)im系统,例如qq或者微博,每个人都读自己的数据(好友列表、群列表、个人信息);2)微博系统,每个人读你关注的...
  • redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动。 redis之所以能解决并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,解决...
  • 然后在实际过程中,因为是多线程运行的导致并发问题,即多个用户同时更新一个id的操作,会报table2的唯一性约束。解决思路(先查询后更新): 遇到这个情况首先想到的是在更新/插入之前先查询数据库的数据,看是否...
  • 如何解决多线程并发问题

    千次阅读 2019-04-26 13:45:08
    关于原子性,一个非常经典的例子就是银行转账问题:比如A和B同时向C转账10万元。如果转账操作不具有原子性,A在向C转账时,读取了C的余额为20万,然后加上转账的10万,计算出此时应该有30万,但还未来及将30万写回C...
  • 乐观锁与悲观锁——解决并发问题

    千次阅读 2019-03-03 17:28:21
    文章目录1. 为什么需要锁(并发控制)?2. 并发控制机制3....这就是著名的并发问题。 典型的冲突有: 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失。例如:用户A把值从6改为2...
  • 如何解决Redis的并发竞争问题

    千次阅读 2019-05-09 17:56:22
    redis的并发竞争问题是什么?如何解决这个问题?了解Redis事务的CAS方案吗? 2 考点分析 这个也是线上非常常见的一个问题,就是多客户端同时并发写一个key,可能本来应该先到的数据后到了,导致数据版本错了。或者是...
  • 点击上方蓝色字体,选择“标星公众号”优质文章,第一时间送达作者:软件编程指南来源:http://suo.im/66liCE本文前篇是对场景的分析,后篇会有解决方案,读完本篇你将可以仅仅...
  • 微服务架构中如何处理高并发问题 处理高并发的问题有3种方法: 1、异步 提高业务过程中可异步部分的占比,提高异步部分的执行效率 2、缓存 将频繁访问的数据存储在离业务处理逻辑更近的地方 3、池化 对于创建起来比较...
  • 并发问题是绝大部分的程序员头疼的问题, 但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研究一下常见的并发和同步吧。 为了更好的理解并发和同步,我们需要先明白两个重要的概念:...
  • java怎么处理高并发

    千次阅读 2021-02-26 21:38:59
    java处理高并发的方法:1、优化代码,减少不必要的资源浪费;2、把图片与页面进行分离,将图片放到独立的图片服器;3、使用缓存,可以大量减少与数据库的交互,提高性能;4、使用数据库集群;5、进行DB优化;6、硬件...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 786,626
精华内容 314,650
关键字:

如何解决并发的问题