java多线程3--线程的高并发

一、并发问题的产生和解决

1、多线程的作用

  CPU、内存、I/O 设备的速度是有极大差异的

  为了合理利用 CPU 的高性能,平衡这三者的速度差异,

   计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:

(1)CPU 增加了缓存,以均衡与内存的速度差异-------导致 可见性问题

(2)操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异; 

                                                                                               ------导致 原子性问题

(3)编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。------导致 有序性问题

2、Java解决并发问题---JMM  Java内存模型

   (1)JMM作用:定义程序中各种共享变量的访问规则

   (2)基本原则:只要不改变程序执行结果,编译器和处理器可任意优化

   (3)规定所有变量都存储在主内存,每条线程有自己的工作内存,线程通信必须经过主内存

      下面详述

 此部分来源:Java 并发 - 理论基础 | Java 全栈知识体系

二、并发的三大特性

1、原子性---任何时刻只能有一个线程,同步----lock,synchronized

       lock-----JMM的lock和unlock

       synchronized-----monitorenter  monitorexit

2、可见性---一个线程修改了共享变量值,其他线程能立即得知---volatile,                                                                                                                 synchronized,final

  •     volatile---保证新值能立即同步到主内存,

                        每次使用前立即从主内存刷新---保证当前值都是修改后的新值

  •     synchronized----lock前清空变量值,从主内存中重获取----锁前取新值

                                     unlock前必须将变量同步回主存-----解锁前更新新值

  •      final----构造方法初始化且构造方法没有把this引用传递出去----其他线程可看见final字段值

  3、有序性----所有操作有序执行----volatile,synchronized

        volatile---禁止指令重排

         synchronized---保证一个变量在同一时刻只允许一条线程对其lock操作

                              ----确保持有同一个锁的两个同步块只能串行进入

三、并发理论

1、CAS---比较并交换

         V--现在内存位置的值---新值

         A--线程操作前复制到自己工作空间的值----原始值

         B--本线程操作后的新值----修改值

      当V=A时-----说明没有其他线程对此值修改----B更新V的值

2、JMM---java内存模型

(1)所有的变量(实例字段,静态字段,构成数组对象的 元素,不包括局部变量和方法参数)都存储在主内存中,

       每个线程有自己的工作内存,线程的工作内存保存被线程使用到变量的主内存副本拷贝。线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存的变量。

       不同线程之间也不能直接访问对方工作内存中的变量,线程间变量值的传递通过主内存来完成。

(2) 八大原子操作:

 主内存中读数据lock---->主存中数据---read-->工作内存---load-->在工作内存中复制一个副本----use--->执行引擎(线程)

                         unlock <------ 写入主内存<---write---主内存<---store---工作内存 <--assign--线程中操作得到的值

 3、as-if-serial---保证单线程执行结果不改变

         (1)原理:保证编译器和处理器不会对存在数据依赖关系的操作 进行重排序

         (2)重排序存在数据依赖关系的操作---改变执行结果

                            不存在数据依赖关系的操作---可能会被重排序----只要保证执行结果不变就行

 4、happens-before----保证同步的多线程执行结果不变

      (1)背景:

              JVM会对代码进行编译优化,会出现指令重排序情况,

               为了避免编译优化对并发编程安全性的影响,需要一些规则。

      (2)目的:

                    定义一些禁止编译优化的场景保证并发编程的正确性

      (3)原理:

                   指定某些特定情况的执行顺序规则----保证并发的正确性

      (4)happens-before规则       

  •       程序次序规则:在一个线程内一段代码的执行结果是有序的。【按代码顺序执行】

               就是还会指令重排,但是随便它怎么排,结果是按照我们代码的顺序生成的,不会变

  •       管程锁定规则就是无论是在单线程环境还是多线程环境,对于同一个锁来说,一个线程对这个锁解锁之后,另一个线程获取了这个锁都能看到前一个线程的操作结果

                                                                                               【前线程unlock先于后一个线程lock】

                管程是一种通用的同步原语------synchronized实现

  •       volatile变量规则:就是如果一个线程先去写一个volatile变量,然后另一个线程去读这个变量,那么线程写操作的结果一定对读的线程可见。       【写操作先于读操作】

  •      线程启动规则在主线程A执行过程中,启动子线程B, 【线程start先于此线程其他操作】

                                     那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见

  •      线程终止规则:在主线程A执行过程中,子线程B终止,【终止操作后于此线程其他操作】

                                    那么线程B在终止之前对共享变量的修改结果在线程A中可见

                                    也称线程join()规则。

  •      线程中断规则:对线程interrupt()方法的调用先行发生于 被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到线程是否发生中断

                                                                            【interrupt先于InterruptedException异常的触发】

      

  •       传递性规则:这个简单的,就是happens-before原则具有传递性,即hb(A, B) , hb(B, C),那么hb(A, C)。                                                                           

  •        对象终结规则:一个对象的初始化的完成,即构造函数执行的结束一定先发生于它的finalize()方法执行。                                                                          【对象初始化先于回收】

    【注】

        1、interrupt()---不能直接中断线程执行,只能改变中断状态,主要用来唤醒阻塞中线程

             详见:                     Java多线程1---线程基础:创建线程三种方式、线程常用方法、线程的生命周期、线程通信等_@snow'的博客-CSDN博客

        2、线程中断规则理解: 如果在线程t1上面调用 interrupted(), t1.interrupted(). 线程一就会停止,不会触发InterruptedException.   

 5、指令重排

       编译器重排序----重排语句的执行顺序

       处理器重排序----重拍机器指令的执行顺序

       内存重排序----内存系统重排序

6、并发关键字:volatile、synchronized,final

 (1)volatile---保证可见性和有序性

  •  保证了新值能立即存储到主内存,每次使用前立即从主内存中刷新----立即刷新---可见性
  • 禁止指令重排序优化----禁止重排----有序性     

 (2)synchronized--保证原子性、可见性和有序性

  (3)final---保证可见性

四、并发工具类---JUC原子类

1、更新变量---atomic

    原子操作类能够用一种用法简单、性能高效、线程安全的方式更新一个变量----Unsafe实现

 (1)一般数据类型

          AtomicInteger  原子更新整型

         AtomicLong      原子更新长整型

         AtomicBoolean  原子更新布尔型

(2)数组类型

          AtomicIntegerArray  原子更新整型数组中元素

         AtomicLongArray      原子更新长整型数组中元素

         AtomicReferenceArray  原子更新引用类型数组中元素

(3)引用类型

       AtomicReference                 原子更新引用类型

      AtomicMarkableReference   原子更新带标记位引用类型

      AtomicStampedReference   原子更新带版本号引用类型----解决CAS中ABA问题

2、计数器---CountDwnLatch

 一个基于执行时间的同步类,允许一个或多个线程等待其他线程完成操作

3、循环屏障---CyclicBarrier

  •     一种基于同步到达某个点的信号量触发机制
  •   作用:让一组线程到达一个屏障时被阻塞,知道最后一个线程到达屏障才会解除

4、信号量----Semaphore

  •   用来控制同时访问特定资源的线程数量---流量控制
  •  构造方法中设置一个int值---最大并发数

5、数据交换---Exchnger

  •     提供一个同步点,用于线程间进行数据交换-----两个线程在同步点进行数据交换
  •     实现方式---exchange()

          第一个线程执行exchange()方法后,会阻塞第二个线程执行该方法,

         当两个线程都到达同步点时,两线程即可交换数据

五、并发容器

(一)HashMap----ConcuurentHashMap

      1、hashMap----非线程安全

          (1)底层结构=数组+链表----一维数组的元素是链表

                  链表----解决key的hash碰撞问题

           (2)当链表长度>8时,转成红黑树----几率很小,千万分之一

                  原因:数组扩容----超过数组的0.75就扩容----扩容2倍

           (3)数组初始长度16----0--15

                  15二进制----1111

                  每个key会对应一个hashCode-----多位的二进制数

           (4)哈希算法:

                   key的hashcode和15的二进制数进行与操作---得到一个哈希值-----数组下标 

                   下标相同---则用链表形式存在同一个数组下标下

                   

                   [注]1、key和15进行与操作,得到的值范围[0-15]

                         2、数组扩容一倍,则key和31进行与操作----值范围[0-31]

    2、ConcuurentHashMap---线程安全

         线程安全的实现:   

     (1)JDK1.7  数组+Segment+分段锁

           在数组基础上增加了一层Segment,一个Segment对应数组的一段,这样对某段进行的操作只需要锁住对应段,不影响其他段的操作

           Segment继承了ReentrantLock并实现了序列化(Seralizable)接口,说明Segment的锁是可重入的。

        (2) JDK1.8 数组+链表+红黑树-----与HashMap保持一致,

             取消了Segment分段锁的数据结构,取而代之的是Node,Node的value和next都是由volatile关键字进行修饰,可以保证可见性

             线程安全实现上,采用CAS+Synchronized替代Segment分段锁

             CAS:比较并交换,详见JUC面试题

                                     

(二)ArrayList---CopyOnWriteArrayList

    1、作用

         读写分离,保证读的数据安全

         读不阻塞,写阻塞---多线程写排队

    2、原理

         读---不加锁 

         写----采用复制新集合的形式---加volatile和lock锁,控制同步,避免多线程copy多个副本

        

        初始化的时候只有一个容器,很长一段时间,这个容器数据、数量等没有发生变化的时候,大家(多个线程),

        都是读取(假设这段时间里只发生读取的操作)同一个容器中的数据,所以这样大家读到的数据都是唯一、一致、安全的,

        但是后来有人往里面增加了一个数据,这个时候CopyOnWriteArrayList 底层实现添加的原理是

        先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,

        但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。

    3、适合场景:读多,写少

   

(三)Set-----CopyOnWriteArraySet---不重复的集合

         CopyOnWriteArraySet的“线程安全”机制,和CopyOnWriteArrayList一样,是通过volatile和互斥锁来实现的

          内部是用CopyOnWriteArrayList实现的,实现不重复的特性也是直接调用CopyOnWriteArrayList的方法实现的

(四)Queue---ConcuurentLinkedQueue

      1>入列出列线程安全,遍历不安全
      2>不允许添加null元素
      3>底层使用列表与cas算法包装入列出列安全

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值