多线程专题

一、基础概念

1、什么是进程?什么是线程?

  进程是os调度的最小单元,比如启动一个java程序就会创建一个进程,线程是cpu调度的最小单元,一个进程可以创建很多个线程,这些线程有各自的计数器、堆栈、局部变量等属性。

2、什么是JMM模型?

  java内存模型(Java Memory Model),并不真实存在,描述的是一组规则或规范,这组规范定义了各个变量的访问方式。JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存,用于存储线程私有的数据,JMM规定所有变量都存储在主内存,主内存是共享区域,所有的线程都可以访问,但是线程变量的操作必须在工作内存中进行,首先将变量拷贝到自己的工作内存空间,然后进行操作,操作完成后回写主内存。不同线程之间无法访问对方的工作内存,线程间的通信必须通过主内存完成。

  主内存:实例共享区域,包括类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问时可能会发生线程安全问题。

  工作内存:存储当前方法的所有本地变量信息副本,每个线程只能访问自己的工作内存,即线程中的本地变量对其他线程是不可见的,就算两个线程执行的是同一段代码。

 

数据同步的八大原子操作

  1. lock(锁定):作用于主内存的变量,把一个变量标记为一条线程独占状态
  2. unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  3. read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  4. load(载入):作用于工作内存中的变量,把read操作从主内存中获取到的变量写入到工作内存的副本中
  5. use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
  6. assign(赋值):作用于工作内存中的变量,把执行引擎接收到的值传送到主内存的变量
  7. store(存储):把工作内存中的值传送到主内存,以便后续的write操作
  8. write(写入):把store操作从工作内存中一个变量的值写入到主内存

同步规则分析

  1. 不允许一个线程无原因的(没有发生过任何assign操作)把数据从工作内存同步回主内存中
  2. 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化的变量。即对一个变量实施use和store操作之前,必须先自行load和assign操作
  3. 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一线程重复执行多次,lock与unlock必须成对出现
  4. 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量之前需要重新执行load和assign操作
  5. 如果一个变量事先没有被lock操作锁定,则不允许对他执行unlock操作
  6. 执行unlock之前,必须先把此变量同步到主内存中(执行store和write操作)

并发编程的可见性,原子性与有序性问题

  1. 原子性:是指一个操作是不可中断的,即使多线程环境下,一旦开始就不会被其它线程影响。基本数据类型都是原子操作(32为操作系统的long和double不一定,因为long和double是64位存储单元,虚拟机读取到的可能是半个变量的值)
    解决:通过syncchronized和lock能保证任意时刻只有一个线程访问该代码块
  2. 可见性:理解指令重排之后,可见性就容易理解了,指的是,当一个线程修改了某个共享变量的值,其它线程是否能够马上得知这个修改的值
    解决:volatile关键字可保证可见性,syncchronized和lock也能保证可见性,因为他们可以保证同一时刻只有一个线程能访问共享资源,并在释放锁之前将修改值刷新到主内存中  
  3. 有序性:如果是多线程环境下,指令重排后的顺序与原指令未必一致
    解决:valatile可以保证一定的有序性,syncchronized和lock可以保证有序性,相当于同一时刻只有一段程序执行同步代码,自然保证了有序性

happens-before原则

  1. 程序顺序原则:即在一个线程内,必须保证语义的串行性,也就是说按照代码顺序执行
  2. 锁规则:解锁动作必须在加锁之前,那么加锁动作也必须在解锁之后
  3. volatile原则:简单理解就是volatile变量在每次线程访问时,都强迫从主变量中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存中
  4. 线程启动规则:start方法先于他的每个动作,A在B执行start方法之前修改了共享变量的值,那么这个值对线程B可见
  5. 传递性:A先于B,B先于C,那么A必然先于C
  6. 线程终止规则:线程所有的操作先于线程的终结,Thread.join()的作用是等待当前执行的线程终止
  7. 线程中断规则:可以通过Thread.interrupted()方法检测线程是否中断
  8. 对象的终结规则:结束先于finalize()方法。

volatile内存语义

  1. 保证被volatile修饰的共享变量对所有的线程总数是可见的,也就是当一个线程修改了被volatile修改的变量值,新值总是可以被其他线程立即得知,但是volatile无法保证原子性
  2. 禁止指令重排优化
    memory = allocate();//1、分配对象内存空间
    instance(memory);//2、初始化对象
    instance = memory; // 3、设置instance指向刚分配的内存地址
    2、3之间可能存在指令重排,而这就产生了一致性的问题,volatile可以禁止这种指令重排

  3. volatile重排序规则:
    • 第二个操作是volatile写时,不管第一个操作是什么,都不能重新排序。这个规则确保volatile写之前的操作不会被编译器重排到volatile读之前
    • 当第一个操作时volatile读时,不管第二个操作是什么,都不能重新排序。这个规则缺包volatile读之后的操作不会被编译器重排到volatile读之前
    • 第一个是volatile写,第二个是volatile读时不能重排序

 

posted @ 2020-11-04 17:02  vvning  阅读(69)  评论(0编辑  收藏  举报