java 高并发知识点学习总结(一)

并发 : 同时拥有两个或多个线程,如果程序在单核处理器上运行,多个线程交替的换入或者换出内存,这些线程是同时存在的,每个线程都处于执行过程中的某个状态,如果运行在多核处理器上,此时,程序中的每个线程都将分配到一个处理器核上,因此可以同时运行。

为什么需要cpu cache: cpu 的频率太快,快到主存跟不上,这样在处理器始终周期内,CPU常常需要等待主存,浪费资源。所以cache得出现,是为了缓解CPU和内存之间速度的不匹配问题。
  CPU cache 有什么意义:
   1) 时间局部性: 如果某个数据被访问,那么在不久的将来它可能被再次的访问
   2) 空间局部性: 如果某个数据被访问是,那么与他相邻的数据块可能会被很快的访问
  CPU多级缓存 --缓存一致性 (MESI--缓存协议)
  用于保证多个CPU cache 之间缓存共享数据的一致
 
CPU多级缓存-乱序执行优化
 处理器为提高运算速度而做出违背代码原有顺序的优化
 
java内存模型规范(java Memory Model,JMM)
 定义:java内存模型规范规定了一个线程如何以及何时可以看到由其他线程修改过的共享变量的值,以及在必须时如何同步的访问共享变量。

java内存中的堆: 可以在运行时动态的分配运行时内存,因此运行效率相对慢一些。
  java中的栈: 优势: 存取速度要比java 中的堆的 存取速度要快,栈中的数据是可以共享的,但是栈中数据的生存期与大小是确定的。栈中主要存放一些基本的数据变量。
  通常情况下,当cpu 需要读取主存的时候,会先将主存中的数据读取到缓存中,甚至会将缓存中的某些数据读取到内部寄存器中,
  在寄存器中执行某些操作。
  java内存模型 - 同步的八种操作
  lock(锁定) : 作用于主内存的变量,把一个变量标识为一条线程独占状态
  unlock(解锁): 作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放出来之后才可以被其他的线程使用。
  read(读取): 作用于主内存的变量,把一个变量从主内存传输到线程的工作内存中,以便随后的load动作使用。
  load(载入): 作用于工作内存的变量,他把read操作从主内存中得到的变量值放入到工作内存的变量副本中。
  use(使用): 作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
  assign(赋值):  作用于工作内存的变量, 他把一个从执行引擎接受到的值赋值给工作内存的变量。
  store(存储): 作用于工作内存的变量,把工作内存中的一个变量的值传输到主内存中,以便随后的write的操作
  write(写入): 作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中 。
 
java内存模型--同步规则
  如果要把一个变量从主内存中复制到工作内存,就需要按顺序的执行read和load操作,如果把变量从工作内存中同步回之内存中,就要按顺序的执行store 和 write 操作。但java 内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
  不允许read和load、store和write操作之一单独出现
  不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
   不允许一个线程无原因的(没有发生过任何assign操作)把数据从工作内存同步回主内存中
   一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作
   一个变量在同一时刻只允许一条线程对其执行lock操作,但lock操作可以被同一线程重复执行多次,多次执行lock之后,只有执行相同次数的unlock操作,变量才被解锁。lock和unlock必须是成对出现的。
   如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load和assign操作初始化变量的值。
  如果一个变量事先没有被lock操作锁定,则不允许对他执行unlock操作,也不允许unlock一个被其他线程锁定的变量
 
  线程安全性 :
   原子性: 提供了互斥访问,同一时刻只能有一个线程来对他进行操作
   可见性: 一个线程对主内存的修改可以及时的被其他线程观察到
   有序性: 一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序
 
   CAS线程安全底层原理: 使用了compareAndSwapInt方法,不断的使用当前的数值与底层主存中的值进行比较,只有当想听那个的时候,才进行返回。
   actomicXXX类: 保证线程的安全性。 、
   JDK1.8 : LongAdder 该类的作用和atomicLong 类的作用是一样的。
   LongAdder类的作用: 主要是对热点数据的一个分离,进行单独处理。
   在低并发的时候,性能和atomicLong 的性能是一致的,但是高并发的时候,通过分散提高了性能,但是在统计的时候,如果有并发更新也会导致统计误差。
   对于需要生成准确的数值,具有全局唯一性数值的时候,atomicLong才是唯一的选择 。
   
   atomicReference,AtomicReferenceFieldUpdate:
   atomicReference:
 
   AtomicStampReference: CAS的ABA问题   假如 某个数据被修改为A然后修改为B再修改为A,该数据的版本就会发生变化
     
    原子性锁:
    synchronized: 依赖JVM (作用对象的作用范围内同一时刻只能有一个对象对其进行操作)
    Lock:依赖特殊的CPU指令,代码实现,ReentrantLock  
   
    synchronized:
    修饰代码块: 大括号括起来的代码,作用于调用的对象
    修饰方法:  整个方法,作用于调用的对象
    修饰静态方法:  整个静态方法,作用于所有的对象
    修饰类: 括号括起来的部分,作用于所有的对象
    synchronized: 在方法定义中synchronized并不是方法的一部分,所以当synchronized修饰的方法被继承的时候,子类是没有synchronized功能的。

可见性: 导致共享变量在线程间不可见的原因:
     线程交叉执行  
     重排序结合线程交叉执行
    共享变量更新后的值没有在工作内存与主存之间及时的更新
    
   JMM关于synchronized的两条规定 :
    线程解锁前,必须把共享变量的最新值刷新到内存中。
    线程加锁时,将清空工作内存中共享变量的值,从而使用共享内存时需要从主内存中重新进行读取
   
     volatile:  通过加入内存屏障和禁止重排序优化来实现
     对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存中
     对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量
      
    volatile不具备原子性:
    适用的场景:  1、 对变量的写错做不依赖于当前的值
     2、 该变量没有包含在其他变量不变的实时中
    很适合做状态标识变量
    
 java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序的过程不会影响到单线程的执行,却会影响到多线程的并发执行正确性
    volatile、synchtroniezed、lock
 
   程序次序规则:一个线程内,按照代码顺序,书写在前面的操作现行发生于书写在后面的操作
   锁定规则: 一个unlock操作先行发生于后面对同一个锁的lock操作
   volatile变量规则: 对一个变量的写操作先行发生于后面对这个变量的读操作
    传递规则: 如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
     线程启动规则: Thread对象的start() 方法先行发生于此线程的每一个动作
     线程中断操作: 对线程interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生
      线程终止规则: 线程中所有的操作都先发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
     对象终结规则: 一个对象的初始化的完成会先行发生于他的finalized() 的方法的时开始
     
 
     对象发布:
   
     发布对象: 使一个对象能够被当前范围之外的代码所使用
     对象逸出: 一种错误的发布。当一个对象还没有构造完成时,就使他被其他线程所见。

展示对象逸出代码示例:

package MyStudyTest;

/**
 * 无论是显示的引用还是隐式的引用都会造成this的逸出,因为构造方法还没有初始化完成,其他的线程就看到了该成员变量的值
 *  错误原因: 在对象还没有完成构造函数初始化之前就对其发布
 */
public class Example2 {
  private int thisCanBeEscape = 0;

  public Example2(){

      new InnerClass();
  }

  private class InnerClass{

      public InnerClass(){
          System.out.println("{}"+Example2.this.thisCanBeEscape);
        }

    }

如果一个对象是一个可变对象,就需要对其进行安全的发布,否则就会造成线程的不安全。

安全发布对象的四种方法:

1、在静态初始化函数中初始化一个对象的引用

2、将对象的引用保存到volatile类型域或者AtomicReference对象中。

3、将对象的引用保存到某个正确构造对象的final类型域中

4、将对象的引用保存到一个有锁保护的域中

volatile关键字有两个使用场景:

1、 可以做状态标识量 (因为该关键字会将工作内存空间中的数据刷新到主内存中)

2、可以进行双重检测(因为该关键字可以限制寄存器发生指令重排--针对单例懒汉模式的双重检测机制)

package MyStudyTest;

/**
 * 懒汉模式:
 * 单例实例在第一次使用的时候创建
 * 线程不安全的,只适用于在单线程的情况下使用
 */
public class SingleTenExample {
    //私有构造函数,只有当构造函数式私有的时候,才不会被其他的引用随便的创建出来
    private SingleTenExample(){};

    //单例
    private  static volatile SingleTenExample instance = null;
   //静态工厂方法
   /* public static SingleTenExample getInstance(){
        if(instance == null){
            return new SingleTenExample();
        } }*/

    //添加synchronized关键字,保证线程安全性
       /* public  static Synchronized SingleTenExample getInstance(){
            if(instance == null){
               instance =  new SingleTenExample();
            }
            }*/

      //以上方法虽然实现了线程的安全性,但是在性能方面较差
     //双重同步锁单例模式
    //这种方式依然是线程不安全的
    //内存寄存器依次完成的指令:
    // 1、memory = allocate() 分配对象的内存空间
    // 2、ctorInstance() 初始化对象
    // 3、instance = memory 设置instance指向刚分配的内存

    // JVM和cpu优化,发生了指令重排

    // 1、memory = allocate() 分配对象的内存空间
    // 3、instance = memory 设置instance指向刚分配的内存
    // 2、ctorInstance() 初始化对象
    //

    /**
     *  双重检测机制不一定是线程安全的,原因是由于有指令重排发生的可能,
     *  当有两个线程同时调用getinstance方法的时候,假如A线程刚好执行到instance = new SingleTenExample(),指令2和3发生了顺序交换,
     *  那么当线程B请求instance ==null的时候,就会默认已经创建了实例,返回instance,而实际上该线程还没有进行初始化,就会导致线程不安全
     *
     *  注: 通过volatile关键字就能限制发生指令重排
     */

    public static SingleTenExample getInstance() {
        if (instance == null) { // 双重检测机制        // B
            synchronized (SingleTenExample.class) { // 同步锁
                if (instance == null) {
                    instance = new SingleTenExample(); // A - 3
                }
            }
        }
        return instance;
    }

}

注:静态域和静态代码款的执行顺序不一样,产生的结果就会不一样

使用枚举来实现单例模式,同时也是一种很安全的单例模式

package com.mmall.concurrency.example.singleton;

import com.mmall.concurrency.annoations.Recommend;
import com.mmall.concurrency.annoations.ThreadSafe;

/**
 * 枚举模式:最安全
 */
@ThreadSafe
@Recommend
public class SingletonExample7 {

    // 私有构造函数
    private SingletonExample7() {

    }

    public static SingletonExample7 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton {
        INSTANCE;

        private SingletonExample7 singleton;

        // JVM保证这个方法绝对只调用一次
        Singleton() {
            singleton = new SingletonExample7();
        }

        public SingletonExample7 getInstance() {
            return singleton;
        }
    }
}

对于为什么枚举是最安全的详见博客: https://www.cnblogs.com/wcgstudy/p/11408495.html

不可变对象:

不可变对象需要满足的条件:

1、对象创建以后,其状态就不能修改

2、对象所有的域都是final类型的

3、对象时正确创建的(对象创建期间,this引用没有逸出)

final: 可以用来修饰类、对象、方法

修饰的类不能被继承 ,final修饰的类中的所有方法都会被隐式的指定为final方法

修饰方法: 1、锁定方法不被继承类修改  2、效率(在JDK早期版本中将方法作为一个内嵌方法,来提升效率,但是当方法过于庞大时,效率不会被提升)

2、private修饰的方法会被隐式的转换为final修饰的方法

修饰变量: 1、基本数据类型变量   2、引用数据类型变量 (被初始化之后,不能指向另外的一个对象)

当final修饰map的时候,只是引用不允许指向另外的一个对象,但是里面的值是允许进行修改的 。

java中其他不可变对象的申明方法:

1、 Collections.unmodifiableXXX:Collection、list、set、Map

2、 Guava : ImmutableXXX: Collection、list、set、Map

被以上两个方法修饰的容器中的数据是不可变的

代码演示实例:

package com.mmall.concurrency.example.immutable;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.mmall.concurrency.annoations.ThreadSafe;

@ThreadSafe
public class ImmutableExample3 {

    private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);

    private final static ImmutableSet set = ImmutableSet.copyOf(list);

    private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4);

    private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder()
            .put(1, 2).put(3, 4).put(5, 6).build();

    public static void main(String[] args) {
        System.out.println(map2.get(3));
    }
}

线程封闭: 就是将对象封装到一个线程中,这样即使这个对象是线程不安全的,也不会有线程不安全的问题存在

实现线程封闭的几种方式:

1、AD-HOC线程封闭: 程序控制实现,最糟糕,可以忽略

2、堆栈封闭: 使用局部变量,无并发问题

3、ThreadLocal 线程封闭: 特别好的线程封闭方法,ThreadLocal底层使用了一个Map实现了线程的封闭

原文地址:https://www.cnblogs.com/wcgstudy/p/11504147.html

时间: 2024-10-21 00:39:08

java 高并发知识点学习总结(一)的相关文章

java高并发----个人学习理解汇总记录

1.首先,需要理解几个概念 1.同步(Synchronous):同步方法调用一旦开始,调用者必须等到前面的方法调用返回后,才能继续后续的行为,依次直到完成所有. 2.异步(Asynchronous):异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作.异步方法通常会在另外一个线程中“真实”地执行.整个过程,不会阻碍当前调用者的工作. 3.并发(Concurrency):在一个时间段内,多件事情在这个时间段内交替执行.比如说:有两个队列,进入同一个入口,交替进

JAVA高并发程序设计学习-JDK并发包:同步控制一

JDK内部提供了大量的API和框架,这里主要介绍三部分 多线程同步控制方法 线程池,提高线程调度的性能 JDK的并发容器 重入锁:java.util.concurrent.locks.ReenterLock 在代码中,类ReenterLock实现了Runnable,其中有static的变量i,在run()方法中会对i进行自增操作. 自增操作的步骤为:get-set,先获取值再增加值. 如果在这里不进行控制的话,会导致get的值不是最新set的值. 因此,在自增的时候使用锁进行控制,保证get-s

JAVA高并发程序设计学习:Synchronized同步代码块具体使用方法

多线程同时对资源进行访问时,同步机制使得同一时间内只能有一个线程对资源进行操作. 同步机制可以用Synchronized实现. 当Synchronized修饰一个方法的时候,该方法称为同步方法. 当Synchronized方法执行完成或者异常时会释放锁. 会有同学对synchronized修饰方法,静态方法,对象时具体对哪些东西加锁不是很明白,这里会进行详细的讲解. synchronized修饰方法时,会对类实例进行加锁,该实例的所有synchronized方法必须等当前锁释放后才能访问. sy

Java高并发,如何解决,什么方式解决

对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了.而并发问题是绝大部分的程序员头疼的问题, 但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研究一下常见的并发和同步吧. 为了更好的理解并发和同步,我们需要先明白两个重要的概念:同步和异步    1.同步和异步的区别和联系 所谓同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到 返回的值或消息后才往下执行其它的命令. 异步,执行完函数或方法

我的《实战java高并发程序设计》纸质书上市了

在过去单核CPU时代,单任务在一个时间点只能执行单一程序,随着多核CPU的发展,并行程序开发就显得尤为重要. <实战Java高并发程序设计>主要介绍基于Java的并行程序设计基础.思路.方法和实战.首先,立足于并发程序基础,详细介绍Java中进行并行程序设计的基本方法.第二,进一步详细介绍JDK中对并行程序的强大支持,帮助读者快速.稳健地进行并行程序开发.第三,详细讨论有关"锁"的优化和提高并行程序性能级别的方法和思路.第四,介绍并行的基本设计模式及Java8对并行程序的支

【实战Java高并发程序设计 1】Java中的指针:Unsafe类

是<实战Java高并发程序设计>第4章的几点. 如果你对技术有着不折不挠的追求,应该还会特别在意incrementAndGet() 方法中compareAndSet()的实现.现在,就让我们更进一步看一下它把! public final boolean compareAndSet(int expect, int update){ returnunsafe.compareAndSwapInt(this, valueOffset, expect, update); } 在这里,我们看到一个特殊的变

Java高并发是不是你的菜??

自从JAVA5.0增加了最初由DougLea编写的高质量的.广泛使用的.并发实用程序util.concurrent并变成了JSR-166的新包之后,在Java内置所提供的类库中,就提供了越来越多的并发编程的实用工具类.学习并掌握这些技术对于专注于Java并发编程的开发人员来讲是基本的公里,随着Java版本的不断更新与改进,开发人员可以通过Java新版本所带来的新特性,无需从头重新编写并发程序工具类. 我们该学习Java并发嘛? 我们该如何学习Java并发? CPU这么多核了,我们如何更好的利用?

Java高并发秒杀系统API之SSM框架集成swagger与AdminLTE

初衷与整理描述 Java高并发秒杀系统API是来源于网上教程的一个Java项目,也是我接触Java的第一个项目.本来是一枚c#码农,公司计划部分业务转java,于是我利用业务时间自学Java才有了本文,本来接触之初听别人说,c#要转java很容易,我也信了,但是真正去学习的时候还是踩了无数个坑,好在朋友有几个做安卓的,向他们讨教了一些经验,但是他们做安卓的和web又是两个方向,于是继续一个人默默采坑避雷之旅,首先上手的是下面这个Java高并发秒杀系统API. 学习java的初衷一个是公司转行,二

java高并发

转自:https://www.cnblogs.com/lr393993507/p/5909804.html 对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了.而并发问题是绝大部分的程序员头疼的问题, 但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研究一下常见的并发和同步吧. 为了更好的理解并发和同步,我们需要先明白两个重要的概念:同步和异步    1.同步和异步的区别和联系 所谓同步,可以理解为在执行完一个函数或方法之后,一直等待系统返