java多线程、高并发

1、synchronized

JDK早期,synchronized 叫做重量级锁, 因为申请锁资源必须通过kernel, 系统调用。涉及到操作系统让线程从用户态切换到内核态。,切换成本很高。

1.6之后引入synchronized优化,就有了偏向锁和轻量级锁。因为发现程序大多数时间都不会发生多个线程公式访问共享资源的情况,每次都加解锁比较耗性能。

原理:

一是了解对象头,二是了解Monitor.加锁的对象都有一个与之关联的Monitor对象,这个对象中有一个线程内部竞争锁的机制(各个队列)。

java多线程、高并发

注意:

1)synchronized对同步代码块和同步方法处理不同。同步语句块的实现使用的是monitorenter 和 monitorexit 指令。方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor,方法完成时释放monitor。

2)synchronized是非公平锁,因为它首先尝试自旋,不行的话才进入队列,如果自旋时抢占到,那么对提前进入队列的线程不公平。

2、CAS

而CAS即是轻量级锁(自旋锁),最终实现:

cmpxchg = cas修改变量值

java多线程、高并发

注意:偏向锁和自旋锁都是由用户空间进行完成的,而重量级锁是需要向内核申请的。

3、锁升级

因为synchronized内部有了相应优化,所以才会有锁升级的概念。

步骤:

java多线程、高并发

为何会有偏向锁?

答:多数synchronized方法在很多情况下,只有一个线程在运行。申请重量级锁没必要(不如直接贴上自己名字)。

何时升级成自旋锁?

答:当有多个线程开始抢,抢的时候使用CAS,此时升级为自旋锁。

何时升级成重量级锁?

答:有线程循环等待10次,升级成重量级(1.6之后的是自适应)。或者有多个线程再等待(超过CPU核数的二分之一)。

注意:线程数少的时候使用自旋,线程数多使用重量级锁。操作时间长使用重量级锁。

4、java对象头

对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。

java多线程、高并发

对于顶部,则是Java头对象,它实现synchronized的锁对象的基础,一般而言,synchronized使用的锁对象是存储在Java对象头里的,jvm中采用2个字来存储对象头(如果对象是数组则会分配3个字,多出来的1个字记录的是数组长度),其主要结构是由Mark Word 和 Class Metadata Address 组成,其结构说明如下表:

java多线程、高并发

java多线程、高并发

5、volatile

首先保持内存可见性,使用缓存一致性协议

二是防止指令重拍序(例如DCL单例),使用了内存屏障,对操作这个变量内存的指令,可以形象理解为在中间加上阻碍不能调换执行顺序。

注意:

多线程并发编程围绕有三个特性 分别是:可见性(作用一)、原子性、有序性(作用三)

volatile并不能保证原子性。而重排序不会对存在依赖关系的操作进行重排。

内存屏障:

1)缓存一致性

java多线程、高并发

解决方法使用缓存一致性协议,协议的类型很多(MSI、MESI、MOSI、Synapse、Firefly),最常见的就是Intel 的MESI 协议。缓存一致性协议主要规范了CPU 读写主存、管理缓存数据的一系列规范。

MESI 协议的核心思想:

  • 定义了缓存中的数据状态只有四种,MESI 是四种状态的首字母。

  • 当CPU写数据时,如果写的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态;

  • 当CPU读取共享变量时,发现自己缓存的该变量的缓存行是无效的,那么它就会从内存中重新读取。

2)JMM

因为有硬件的差异以及编译器和处理器的指令重排优化的存在,所以Java 需要一种协议来规避硬件平台的差异,保障同一段代表在所有平台运行效果一致,这个协议叫做Java 内存模型(Java Memory Model)。是Java 并发编程的核心和基础。

JMM 协议就是一套规范,具体的内容为:

所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量(主内存的拷贝),线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成。

Java 通过 Java 内存模型(JMM )实现 volatile 平台无关。

JMM使用内存屏障提供了java程序运行时统一的内存模型。

在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。

As-if-serial语义的意思是,所有的动作(Action)都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。

6、ReentrantLock--可重入锁

ReentrantLock是基于AQS实现的,这在下面会讲到,AQS的基础又是CAS。

ReentrantLock实现的前提就是AbstractQueuedSynchronizer,简称AQS,是java.util.concurrent的核心,CountDownLatch、FutureTask、Semaphore、ReentrantLock等都有一个内部类是这个抽象类的子类。

java多线程、高并发