0%

Java高并发:基本概念

JVM

Java虚拟机 (Java Virtual Machine)

JMM

Java内存模型 (Java Memory Model),简称JMM 本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

JMM对于同步的规定:

  1. 线程解锁前,必须把共享变量的值刷新成主内存
  2. 线程加锁前,必须读取主内存的最新值到自己的工作内存
  3. 加锁和解锁是同一把锁

主内存是共享内存区域,是所有线程都可以访问的,也就是我们常说的内存。但是线程对变量的操作(读取赋值等)必须在工作内存中进行,首先就是将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存。每个线程都有自己的工作空间,线程间的工作内存不能互相访问。至于为什么要拷贝,主要因为CPU的效率远远高于内存。拷贝到自己的工作内存,实际上就是拷贝到CPU的缓存中去。

JMM特性:

  1. 可见性
  2. 原子性
  3. 有序性

volatile

英[ˈvɒlətaɪl],美[ˈvɑːlətl]

是Java虚拟机提供的轻量级的同步机制,有三大特性:

  1. 保证可见性
  2. 不保证原子性 (synchronized保证原子性)
  3. 禁止指令重排序

可见性与非原子性

可见性就是一个线程修改了主内存的时候,其他线程马上获得通知。

如何解决非原子性问题?比如i++,推荐使用AtomicInteger这些类,其次可以使用synchronized之类的解决方法

指令重排

计算机在执行程序的时候,为了提高性能,编译器和处理器会对指令做重排: 【1.源代码 –> 2.编译器优化的重排 –> 3.指令并行的重排 –> 4.内存系统的重排 –> 最终执行的指令】 其中2,3,4都有可能发生指令重排

  1. 单线程环境里面也会有指令重排,但是会确保执行结果和代码顺序执行的结果一致,所以指令重排在单线程下完全没有问题
  2. 处理器在进行指令重排序的时候必须要考虑指令之间的数据依赖性
  3. 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测

指令重排序可能引入的问题,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ReSortDemo {
int a = 0;
boolean flag = false;

public void fun01() {
a = 1; // 语句1
flag = true; // 语句2
}

public int fun02 {
if (flag) {fuzhi // 语句4
}
}
}

想象一下,多线程场景,fun01方法内部语句发生指令重排(因为a和flag没有数据依赖性),变成先执行语句2再执行语句1。当先执行完语句2还没有执行语句1的时候,另外线程执行fun02方法,此时由于flag==true所以会执行里面的语句3,并且打印5,因为语句1a=1还没有执行,所以结果是5。预期的结果应该是6,发生线程不安全问题。解决方法就是给变量加上volatile修饰符。

volatile应用于懒汉单例,就是因为指令重排问题

CAS

Compare And Swap 介绍

可以通过Java中AtomicInteger类中的getAndIncrement方法来理解

1
2
3
4
5
6
7
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}

比如我想i++,原先i=5,先做运算然后赋值,赋值时比较下当前i是不是还是5,如果i还是5,就直接赋值了,如果不是,说明其他线程做了修改,就需要将这个新的值重新运算,再判断赋值,如果原先的值又变了,再运算判断赋值…

CAS优点

不需要加锁,也就减少线程上下文切换带来的消耗,线程切换好像设计内核态和用户态,这个以后再补充。// TODO

CAS有哪些缺点以及问题

1. 一直循环问题

会锁升级,计算机已经做了优化,不需要在代码中体现

2. ABA问题

Java中的AtomicStampedReference类有解决这个问题,原理是加了一个stamp,相当于一个版本号,如果有修改,版本号就会有变化,这样当从A->B->A就会知道修改过。如果不在意这种修改,其实是可以不用使用stamp的,如果在意这种修改就可以使用这种方法。