一.原子性、可见性、有序性
原子性:
涉及到共享变量访问的操作,除了执行线程之外的任意操作都是不可分割的,那么该操作就是原子操作。即,其他线程不会看到该操作执行了部分的中间结果。
原子性的实现方式:
利用锁的排他性,同一时刻只有一个线程在操作一个共享变量
利用CAS保证
Java语言规范中,保证了除了long和double型意外的任何变量的写操作都是原子操作
Java语言规范中规定,volatile关键字修饰的变量可以保证其写操作的原子性
注意事项:
1.原子性针对的是多个线程的共享变量,所以对于局部变量来说不存在共享问题,也就无所谓是否是原子操作
2.单线程环境下讨论是否原子操作没有意义
3.volatile关键字仅仅能保证变量写操作的原子性,不能保证复核操作,比如说读写操作的原子性
可见性:
指一个线程对于共享变量的更新,对于后续访问该变量的线程是否课件的问题。
处理器缓存:
处理器处理速度远远大于主内存的处理速度,所以在主内存和处理器之间加入了寄存器,高速缓存,写缓冲器预计无效化队列等部件来加速闪存的读写操作。也就是说我们处理器可用于这些部件进行读写操作的交互,这些部件可以称为处理器缓存。
如何保证可见性:
当前处理器需要刷新处理器缓存,使得其余处理器对变量所进行的操作可以同步到当前的处理器缓存中
当前处理器对共享变量更新后,需要冲刷处理器缓存,使得该更新被写入其他处理器缓存中
有序性:
为了提高程序执行的性能,Java编译器在其认为不影响程序正确性的前提下,可能会对源码顺序进行一定的调整,导致程序运行顺序与源代码顺序不一致。
重排序举例:
instance ins = new instance()都发生了啥?
具体步骤如下所示三步:
1.在堆内存分配对象的内存空间
2.在堆内存上初始化对象
3.设置instance指向刚分配的内存地址
第二步和第三步可能发生重排序,导致引用类型变量指向了一个不为null但是也完整的对象。
在多线程下的单例模式中,我们必须通过volatile来禁止指令重排序。
二.synchronize关键字
synchronize是Java中的一个关键字,是一个内部锁。可以用在方法和方法块上。多线程环境下,同步方法或者同步代码块在同一时刻只允许有一个线程在执行,其余线程都在等待获取锁,实现了整体并发中的局部串行。
内部锁底层实现
进入时,执行monitorenter,将计数器+1,释放锁monitorexit,计数器-1
synchronize内部锁对原子性的保证
锁通过互斥来保证原子性,互斥是指一个锁一次只能被一个线程所持有的,所以,临界区代码只能保证被一个线程执行,即保证了原子性。
synchronize内部锁对可见性的保证
获得锁之后,需要刷新处理器缓存,使得前面写线程所做的更新可以同步到本线程。
释放锁需要冲刷处理器缓存,使得当前线程对共享数据的改变可以被推到下一个线程处理器的告诉缓冲中。
synchronize内部锁对有序性的保证
由于可见性和有序性的保证,使得写线程在临界区中所执行的一系列操作在读线程所执行的临界区看起来像是完全按照源代码顺序执行的,既保证了有序性。,
公平调度和非公平调度
JVM对synchronize内部锁的调度
JVM对内部锁的调度是一种非公平的调度方式,JVM会给每个内部锁分配一个入口集合entrySet,用于记录等待获取相应内部锁的线程。当锁被持有的线程释放的时候,该锁的入口集中的任意一个线程将会被唤醒,从而得到再次申请锁的机会。被唤醒的线程等待占用处理器运行时可能还有其他新的活跃线程与该线程抢占这个被书房的锁。
公平调度
按照申请的先后顺序授予资源的独占权
非公平调度
在该策略中,资源的持有线程释放资源时候,在等待队列中的一个线程会被唤醒,而该线程从被唤醒到其继续执行可能需要一段时间,在该段时间内,新来的线程可以先辈授予该资源的独占权。
三.volatile关键字
volatile关键字是一个轻量级的锁,可以保证可见性和有序性,但是不保证原子性
1.volatile可以保证主内存和工作内存直接产生交互,进行读写操作,保证可见性
2.volatile仅能保证变量写操作的原子性,不能保证读操作的原子性
3.volatile可以禁止指令重排序,通过插入内存屏障,经典案例是在单例模式中使用
volatile变量的开销:
volatile不会导致线程的上下文切换,但是其读取变量的成本较高,因为每次都需要从告诉缓存或者主内存中读取,无法从寄存器中读取变量。
volatile在什么情况下可以替代锁
四.ReentrantLock和synchronize的区别
reentrantlock是显式锁,其提供了一些内部锁不具备的特征,并不是内部锁的替代品。显示锁支持公平和非公平的调度方式,默认采用非公平调度。
synchronize内部锁简单,但是不灵活。显式锁支持在一个方法内申请锁,并且在另一个方法