原子性、可见性、有序性;
1、计算机中的内存模型- 在计算机程序的执行指令中,由于主存的读取速度十分的缓慢,会导致cpu的执行指令效率下降,所以在程序执行时,一般通过cpu的高速缓存进行cpu执行的执行.
- 因此,在多核cpu执行指令时,会导致,同一个共享变量被多个cpu高速缓存进行执行,导致缓存一致性问题,
2.可见性问题一个操作的执行过程不会被其他的操作进行打断,要么执行完成,要么一开始不执行。
3.有序性问题当多个线程同时操作一个变量,若一个线程修改了变量的值,其他的线程可以看到修改后的值
3、Java中的内存模型指令重排序:程序为了提高cpu的执行效率,会将需要进行执行的语句进行重排序,所以不保证程序运行的顺序,但保证重排后的执行结果与程序的执行结果是一致的。注:若数据间存在依赖性,则不会进行重排序
- Java中的内存模型与计算机中的内存模型有所不同:
- Java内存模型规定,所有的变量都存在Java主存中,而且每个线程都有自己的主存
- Java中并不限制高速缓存器的使用,因此也会存在原子性、可见性以及一致性的问题
x = 10; //语句1 y = x; //语句2 x++; //语句3 x = x + 1; //语句4
上诉语句中,只有语句1是原子性操作,其他语句存在对其他变量的调用,一旦出现异常则程序执行失败,因此Java中的原子性指:只有简单的变量赋值语句才是原子性操作,并且所赋的值必须是数值;
2、可见性3、有序性(指令重排)volatile关键字:在Java中保证可见性,volatile会对数据进行修改时,立即将数据存入主存,其他线程操作数据时,看到的就是最新的数据;
另外synchronize、lock也可以保证可见性;
happens-before原则
在Java中允许计算机对程序执行进行重排序;
Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
- 锁定规则:一个unLock操作先行发生于后面对同一个锁lock操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
- 保证了不同线程对该变量的可见性;(保证可见性)
- 禁止指令重排;(保证有序性)
- 不保证原子性;
原理与实现机制volatile不保证原子性:当i++的操作被volatile修饰时,若处于多线程状态下,数据不一定正常;
5、CAS算法(Compare And Swap)“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
CAS自旋
1、CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令用于管理对共享数据的并发访问。 2、CAS 是一种无锁的非阻塞算法的实现。 3、CAS 包含了 3 个操作数:需要读写的内存值 V、进行比较的值 A、拟写入的新值 B 4、当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。
public class TestCompareAndSwap {
public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int expectedValue = cas.get();
boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
System.out.println(b);
}
}).start();
}
}
}
class CompareAndSwap{
private int value;
//获取内存值
public synchronized int get(){
return value;
}
//比较
public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value;
if(oldValue == expectedValue){
this.value = newValue;
}
return oldValue;
}
//设置
public synchronized boolean compareAndSet(int expectedValue, int newValue){
return expectedValue == compareAndSwap(expectedValue, newValue);
}
}
6、原子变量(Automic…)
7、JUC包中的锁 1、AQS
- 类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将 volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的。
- 类 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。
- AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。
- 核心方法:boolean compareAndSet(expectedValue, updatevalue)
- 什么是AQS?为什么它是JUC锁的核心类?
- AQS的核心思想是什么?他是怎么实现的?底层数据结构是什么?
- AQS有哪些核心的方法?
AQS的核心思想:如果被请求的共享资源空闲,将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态;如果共享资源处于被占用状态,则使用CLH队列锁实现线程阻塞等待以及被唤醒,即:将获取不到锁的线程加入到队列中;
AQS底层数据结构使用CLH队列,是一个虚拟的双向队列(即不存在队列实例,只存在节点之间的关联关系)AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配的。AQS对资源的共享方式
-
独占:只有一个线程可以执行(ReentrantLock)
- 公平锁:按照线程在队列中的顺序,先到先得;
- 非公平锁:无视顺序,进行抢锁;
-
共享:多个线程可以同时执行(Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock)
总结:
- 每一个节点都是由前一个节点唤醒的;
- 当节点发现前驱节点是head并且尝试获得成功,就会轮到该线程执行;
- condition queue中节点向sync queue中转移是通过singal完成了;
- 当节点状态为SINGAL时,后面的节点需要运行;
ConcurrentHashMap - JDK 1.7JDK1.7之前的ConcurrentHashMap使用分段锁机制实现,JDK1.8则使用数组+链表+红黑树数据结构和CAS原子操作实现ConcurrentHashMap;
在JDK1.5~1.7版本,Java使用了分段锁机制实现ConcurrentHashMap. 简而言之,ConcurrentHashMap在对象中保存了一个个Segment数组,即将整个Hash表划分为多个分段;而每个Segment元素,即每个分段则类似于一个Hashtable;这样,在执行put操作时首先根据hash算法定位到元素属于哪个Segment,然后对该Segment加锁即可。因此,ConcurrentHashMap在多线程并发编程中可是实现多线程put操作。接下来分析JDK1.7版本中ConcurrentHashMap的实现原理。数据结构
- 简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承ReentrantLock来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
在JDK1.7之前,ConcurrentHashMap是通过分段锁机制来实现的,所以其最大并发度受Segment的个数限制。因此,在JDK1.8中,ConcurrentHashMap的实现原理摒弃了这种设计,而是选择了与HashMap类似的数组+链表+红黑树的方式实现,而加锁则采用CAS和synchronized实现。数据结构



