皮皮网

【手机充值软件源码】【sd 协议 源码】【支撑指标源码】synchronized的源码_synchronized源码分析

来源:免监听支付源码 时间:2024-12-24 10:28:22

1.synchronized关键字
2.线程安全的list之synchronizedList和CopyOnWriteArrayList
3.MarkWord和Synchronized的码s码分锁升级机制详解(JDK8)
4.初始synchronized关键字的偏向锁、轻量锁、码s码分重量锁
5.源码分析: Java中锁的码s码分种类与特性详解
6.关于@synchronized,你所不知道的码s码分事情

synchronized的源码_synchronized源码分析

synchronized关键字

       并发编程中的关键点在于数据同步、线程安全和锁。码s码分编写线程安全的码s码分手机充值软件源码代码,核心在于管理对共享和可变状态的码s码分访问。

       共享意味着变量可以被多个线程访问,码s码分而可变则意味着变量的码s码分值在其生命周期内可以变化。

       当多个线程访问某个状态变量,码s码分且有一个线程执行写入操作时,码s码分必须使用同步机制来协调对这些线程的码s码分访问。

       Java中的码s码分主要同步机制是关键字synchronized,它提供了一种独占的码s码分加锁方式。

       以下是码s码分关于synchronized关键字的几个方面:

       关键字synchronized的特性:

       不可中断:synchronized关键字提供了独占的加锁方式,一旦一个线程持有了锁对象,其他线程将进入阻塞状态或等待状态,直到前一个线程释放锁,中间过程不可中断。

       原子性:synchronized关键字的不可中断性保证了它的原子性。

       可见性:synchronized关键字包含了两个JVM指令:monitor enter和monitor exit,它能够保证在任何时候任何线程执行到monitor enter时都必须从主内存中获取数据,而不是从线程工作内存获取数据,在monitor exit之后,工作内存被更新后的值必须存入主内存,从而保证了数据可见性。

       有序性:synchronized关键字修改的同步方法是串行执行的,但其所修饰的代码块中的指令顺序还是会发生改变的,这种改变遵守java happens-before规则。

       可重入性:如果一个拥有锁持有权的线程再次获取锁,则monitor的计数器会累加1,当线程释放锁的时候也会减1,直到计数器为0表示线程释放了锁的持有权,在计数器不为0之前,其他线程都处于阻塞状态。

       关键字synchronized的用法:

       synchronized关键字锁的是对象,修饰的可以是代码块和方法,但不能修饰class对象以及变量。

       在开发中最常用的是用synchronized关键字修饰对象,可以控制锁的粒度,所以针对最常用的场景,先来看看它的字节码文件。

       TIPS:在使用synchronized关键字时注意事项

       锁膨胀:

       在jdk1.6之前,线程在获取锁时,如果锁对象已经被其他线程持有,此线程将挂起进入阻塞状态,sd 协议 源码唤醒阻塞线程的过程涉及到了用户态和内核态的切换,性能损耗比较大。

       synchronized作为亲儿子,混的太差肯定不行,在jdk1.6对其进行了优化,将锁状态分为了无锁状态、偏向锁、轻量级锁、重量级锁。

       锁的升级过程既是:

       在了解锁的升级过程之前,重点理解了monitor和对象头。

       每一个对象都与一个monitor相关联,monitor对象与实例对象一同创建并销毁,monitor是C++支持的一个监视器。锁对象的争夺即是争夺monitor的持有权。

       在OpenJdk源码中找到了ObjectMonitor的源码:

       owner:指向线程的指针。即锁对象关联的monitor中的owner指向了哪个线程表示此线程持有了锁对象。

       waitSet:进入阻塞等待的线程队列。当线程调用wait方法之后,就会进入waitset队列,可以等待其他线程唤醒。

       entryList:当多个线程进入同步代码块之后,处于阻塞状态的线程就会被放入entryList中。

       那什么是对象头呢?它与synchronized又有什么关系呢?

       在JVM中,对象在内存中分为3块区域:

       我们先通过一张图了解下在锁升级的过程中对象头的变化:

       接下来我们分析锁升级的过程:

       第一个分支锁标志为:

       当线程运行到同步代码块时,首先会判断锁标志位,如果锁标志位为,则继续判断偏向标志。

       如果偏向标志为0,则表示锁对象未被其他线程持有,可以获取锁。此时当前线程通过CAS的方法修改线程ID,如果修改成功,此时锁升级为偏向锁。

       如果偏向标志为1,则表示锁对象已经被占有。

       进一步判断线程id是否相等,相等则表示当前线程持有的锁对象,可以重入。

       如果线程id不相等,则表示锁被其他线程占有。

       需进一步判断持有偏向锁的线程的活动状态,如果原持有偏向锁线程已经不活动或者已经退出同步代码块,则表示原持有偏向锁的线程可以释放偏向锁。释放后偏向锁回到无锁状态,支撑指标源码线程再次尝试获取锁。主要是因为偏向锁不会主动释放,只有其他线程竞争偏向锁的时候才会释放。

       如果原持有偏向锁的线程没有退出同步代码块,则锁升级为轻量级锁。

       偏向锁的流程图如下:

       第二个分支锁标志为:

       在第一个分支中我们了解到在如果偏向锁已经被其他线程占有,则锁会被升级为轻量级锁。

       此时原持有偏向锁的线程的栈帧中分配锁记录Lock Record,将对象头中的Mark Word信息拷贝到锁记录中,Mark Word的指针指向了原持有偏向锁线程中的锁记录,此时原持有偏向锁的线程获取轻量级锁,继续执行同步块代码。

       如果线程在运行同步块时发现锁的标志位为,则在当前线程的栈帧中分配锁记录,拷贝对象头中的Mark Word到锁记录中。通过CAS操作将Mark Word中的指针指向自己的锁记录,如果成功,则当前线程获取轻量锁。

       如果修改失败,则进入自旋,不断通过CAS的方式修改Mark Word中的指针指向自己的锁记录。

       当自旋超过一定次数(默认次),则升级为重量锁。

       轻量级流程图如下图:

       第三个分支锁标志位为:

       锁标志为时,此时锁已经为重量锁,线程会先判断monitor中的owner指针指向是否为自己,是则获取重量锁,不是则会挂起。

       整个锁升级过程中的流程图如下,如果看懂了一定要自己画一遍。

       总结:

       synchronized关键字是一种独占的加锁方式,不可中断,保证了原子性、可见性和有序性。

       synchronized关键字可用于修饰方法和代码块,但不能用于修饰变量和类。

       多线程在执行同步代码块时获取锁的过程在不同的锁状态下不一样,偏向锁是修改Mark Word中的线程ID,轻量锁是修改Mark Word的指针指向自己的锁记录,重量锁是修改monitor中的指针指向自己。

       今天就学到这里了!收工!

线程安全的list之synchronizedList和CopyOnWriteArrayList

        在上篇文章中我们已经介绍了其他的一些list集合,如ArrayList、linkedlist等。不清楚的可以看下上篇文章 /p/ab5bf7

        但是向ArrayList这些会出现线程不安全的问题,我们该怎样解决呢?接下来就是要介绍我们线程安全的list集合synchronizedList和CopyOnWriteArrayList。

        synchronizedList的使用方式:

        从上面的使用方式中我们可以看出,synchronizedList是将List集合作为参数来创建的synchronizedList集合。

        synchronizedList为什么是线程安全的呢?

        我们先来看一下他的源码:

        我们大概贴了一些常用方法的源码,从上面的源码中我们可以看出,其实synchronizedList线程安全的原因是因为它几乎在每个方法中都使用了synchronized同步锁。

        synchronizedList官方文档中给出的使用方式是以下方式:

        在以上源码中我们可以看出,官方文档是建议我们在遍历的时候加锁处理的。但是既然内部方法以及加了锁,为什么在遍历的时候还需要加锁呢?我们来看一下它的遍历方法:

        从以上源码可以看出,虽然内部方法中大部分都已经加了锁,但是iterator方法却没有加锁处理。那么如果我们在遍历的时候不加锁会导致什么问题呢?

        试想我们在遍历的时候,不加锁的情况下,如果此时有其他线程对此集合进行add或者remove操作,那么这个时候就会导致数据丢失或者是脏数据的问题,所以如果我们对数据的要求较高,想要避免这方面问题的话,在遍历的时候也需要加锁进行处理。

        但是既然是使用synchronized加锁进行处理的,那肯定避免不了一些锁开销。有没有效率更好的方式呢?那就是我们另一个主要的并发集合CopyOnWriteArrayList。

        CopyOnWriteArrayList是在执行修改操作时,copy一份新的数组进行相关的操作,在执行完修改操作后将原来集合指向新的集合来完成修改操作。具体源码如下:

        从以上源码我们可以看出,它在执行add方法和remove方法的时候,分别创建了一个当前数组长度+1和-1的数组,将数据copy到新数组中,然后执行修改操作。修改完之后调用setArray方法来指向新的数组。在整个过程中是使用ReentrantLock可重入锁来保证不会有多个线程同时copy一个新的数组,从而造成的混乱。并且使用volatile修饰数组来保证修改后的可见性。读写操作互不影响,所以在整个过程中整个效率是非常高的。

        synchronizedList适合对数据要求较高的情况,但是因为读写全都加锁,所有效率较低。

        CopyOnWriteArrayList效率较高,适合读多写少的场景,因为在读的时候读的是旧集合,所以它的实时性不高。

MarkWord和Synchronized的锁升级机制详解(JDK8)

       锁升级机制在JDK 后已经废弃,本文所述仅为面试中常问的低版本synchronized的锁升级机制,具体新机制需查阅最新JDK源码。辅助源码教程

       在Java并发编程中,synchronized是最常用的关键字,用于保护代码块和方法在多线程场景下的并发安全问题。synchronized锁基于对象实现,通常用于修饰同步方法和同步代码块。

       下面给出一段简单的Java代码,包含三种synchronized的使用方法,通过反编译查看字节码,了解synchronized的实现原理。

       修饰方法时,synchronized关键字会在方法的字节码中添加ACC_SYNCHRONIZED标志,确保只有一个线程可以同时执行该方法。synchronized修饰静态方法同样添加此标志。

       修饰代码块时,synchronized关键字会在相应的指令区间添加monitorenter和monitorexit指令,JVM通过这两个指令保证多线程状态下的同步。

       ACC_SYNCHRONIZED、monitorenter、monitorexit的解释,来源于官网介绍和chatgpt翻译。

       方法级的synchronized隐式执行,通过ACC_SYNCHRONIZED标志区分,方法调用指令会检查此标志。调用设置ACC_SYNCHRONIZED的方法时,线程进入monitor,执行方法,并在方法调用正常完成或异常中断时退出monitor。

       monitorenter指令尝试获取与对象相关联的monitor的所有权,monitorexit指令执行时,对象相关联的monitor的进入计数减1。

       Monitor是Java中用于实现线程同步和互斥的机制,每个Java对象都与一个Monitor相关联,主要目的是确保在任何给定时间,只有一个线程能够执行与特定对象相关联的临界区代码。

       ObjectMonitor是JDK 的HotSpot源码中定义的Monitor,其核心参数包括EntrySet、WaitSet和一个线程的owner。

       Java对象与monitor关联,需要了解Java对象布局和对象头的相关知识。

       在JDK 1.6之前,synchronized需要依赖于底层操作系统的Mutex Lock实现,导致效率低下。在JDK 1.6之后,引入了偏向锁与轻量锁来减小获取和释放锁的性能消耗。

       锁升级分为四种状态:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁,tinycc源码学习锁会随着线程的竞争情况逐渐升级,但锁升级是不可逆的。

       偏向锁在没有其他线程竞争时,持有偏向锁的线程不会主动释放,偏向锁的释放时机是在其他线程竞争该锁时。

       轻量级锁使用CAS操作,尝试将对象头部的锁记录指针替换为指向线程栈上的锁记录。轻量级锁的撤销意味着不再通过自旋的方式等待获取锁,而是直接阻塞线程。

       重量级锁状态下,对象的头部会指向一个Monitor对象,该Monitor对象负责管理锁的获取和释放。

       JDK 1.6及之后版本引入了自适应自旋锁、锁消除和锁粗化等锁优化策略,以进一步提升synchronized的性能。

       自适应自旋锁根据前一次在相同锁上的自旋时间以及锁的持有者状态来动态决定自旋的上限次数。

       锁消除是JVM在JIT编译期间进行的优化,通过逃逸分析来消除不可能存在共享资源竞争的锁。

       锁粗化是通过将加锁范围扩展到整个操作序列的外部,降低加锁解锁的频率来减少性能损耗。

       本文总结了JDK8中synchronized的锁升级机制,介绍了无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁的升级流程,以提升并发效率。

初始synchronized关键字的偏向锁、轻量锁、重量锁

       作为一名Java程序员,synchronized关键字在日常编码中不可或缺。然而,是否真正理解了synchronized背后的工作原理呢?从性能角度来看,synchronized关键字在早期版本(JDK 1.6之前)只支持重量锁,这意味着线程在加锁时会由用户态切换到内核态,导致性能下降。为了解决这一问题,Doug Lea引入了ReentrantLock类库,其采用纯Java代码实现加锁逻辑,避免了用户态与内核态的切换,从而在多线程竞争同一把锁时,性能显著提高。

       那么,synchronized关键字是如何实现这三种锁类型的?它们分别是偏向锁、轻量锁和重量锁。在JDK 1.6及之后版本中,synchronized引入了这些锁类型,以适应不同场景下的并发需求。从左到右,这三种锁的性能逐渐降低,但它们之间可以相互转换。JVM在特定条件下,如在无锁竞争时使用偏向锁,有锁竞争时转换为轻量锁或重量锁。

       让我们深入探讨每种锁类型的特点。偏向锁在第一次加锁时偏向特定线程,后续加锁操作无需额外判断,性能最高,但若存在其他线程竞争锁,偏向锁会转换为轻量锁或重量锁。轻量锁在多个线程交替执行时使用,同样避免了用户态与内核态的切换。重量锁则支持所有并发场景,当偏向锁或轻量锁无法满足需求时,重量锁会取代它们,导致线程切换。

       随着synchronized关键字引入偏向锁和轻量锁,其性能已经与ReentrantLock相当,甚至在某些情况下,JVM开发者更推荐使用synchronized。除非业务场景需要ReentrantLock的特性,如可打断、条件锁等,通常使用synchronized已经足够。

       接下来,让我们探索JVM是如何判断synchronized给对象加的是什么锁。对象头中的“Mark Word”区域记录了锁的信息。通过“Mark Word”的值,可以判断对象当前所处的锁状态。例如,无锁或偏向锁时,最低几位表示锁状态;轻量锁时,前几位存储指向锁记录的对象;重量锁时,后几位标识重量锁。通过分析“Mark Word”,可以确定对象当前的锁类型。

       动手验证代码,可以实现在不同锁状态下的对象布局信息,如偏向锁、轻量锁和重量锁。通过观察打印结果,可以直观地理解每种锁类型在对象头中的表示方式。在验证代码中,配置了关闭偏向延迟的JVM参数,确保初始对象布局为无锁或偏向锁状态。通过加锁和释放锁的操作,可以观察到“Mark Word”值的变化,从而了解不同锁状态的特性。

       综上所述,synchronized关键字通过引入偏向锁、轻量锁和重量锁,显著优化了并发场景下的性能。理解这些锁类型及其在对象布局中的表示方式,对于深入掌握Java并发编程至关重要。探索JVM底层源码,可以更全面地了解synchronized加锁逻辑的实现细节,为高级并发编程奠定基础。

源码分析: Java中锁的种类与特性详解

       在Java中存在多种锁,包括ReentrantLock、Synchronized等,它们根据特性与使用场景可划分为多种类型,如乐观锁与悲观锁、可重入锁与不可重入锁等。本文将结合源码深入分析这些锁的设计思想与应用场景。

       锁存在的意义在于保护资源,防止多线程访问同步资源时出现预期之外的错误。举例来说,当张三操作同一张银行卡进行转账,如果银行不锁定账户余额,可能会导致两笔转账同时成功,违背用户意图。因此,在多线程环境下,锁机制是必要的。

       乐观锁认为访问资源时不会立即加锁,仅在获取失败时重试,通常适用于竞争频率不高的场景。乐观锁可能影响系统性能,故在竞争激烈的场景下不建议使用。Java中的乐观锁实现方式多基于CAS(比较并交换)操作,如AQS的锁、ReentrantLock、CountDownLatch、Semaphore等。CAS类实现不能完全保证线程安全,使用时需注意版本号管理等潜在问题。

       悲观锁则始终在访问同步资源前加锁,确保无其他线程干预。ReentrantLock、Synchronized等都是典型的悲观锁实现。

       自旋锁与自适应自旋锁是另一种锁机制。自旋锁在获取锁失败时采用循环等待策略,避免阻塞线程。自适应自旋锁则根据前一次自旋结果动态调整等待时间,提高效率。

       无锁、偏向锁、轻量级锁与重量级锁是Synchronized的锁状态,从无锁到重量级锁,锁的竞争程度与性能逐渐增加。Java对象头包含了Mark Word与Klass Pointer,Mark Word存储对象状态信息,而Klass Pointer指向类元数据。

       Monitor是实现线程同步的关键,与底层操作系统的Mutex Lock相互依赖。Synchronized通过Monitor实现,其效率在JDK 6前较低,但JDK 6引入了偏向锁与轻量级锁优化性能。

       公平锁与非公平锁决定了锁的分配顺序。公平锁遵循申请顺序,非公平锁则允许插队,提高锁获取效率。

       可重入锁允许线程在获取锁的同一节点多次获取锁,而不可重入锁不允许。共享锁与独占锁是另一种锁分类,前者允许多个线程共享资源,后者则确保资源的独占性。

       本文通过源码分析,详细介绍了Java锁的种类与特性,以及它们在不同场景下的应用。了解这些机制对于多线程编程至关重要。此外,还有多种机制如volatile关键字、原子类以及线程安全的集合类等,需要根据具体场景逐步掌握。

关于@synchronized,你所不知道的事情

       使用 Objective-C 编写并发程序时,可能会遇到 @synchronized 的使用。它的作用类似锁(lock),防止不同线程同时执行同一段代码。相较于使用 NSLock 创建锁对象、加锁和解锁,@synchronized 更方便、可读性更高。下面通过一个例子来说明它的使用方法。

       假设我们需要实现一个线程安全的队列,通过 @synchronized 结构简化代码实现。在初始阶段,我们可能直接使用 NSLock 实现,但在使用 @synchronized 结构后,代码会更加简洁。

       在前面的例子中,@synchronized 结构与锁操作的效果相同,可以视为锁定 self,确保代码在特定对象上只执行一次。通过左括号 { 和右括号 } 控制锁的获取与释放,省去了手动管理锁的步骤。

       @synchronized 结构可以应用到任何 Objective-C 对象上,使用 @synchronized(_elements) 相当于锁定 self。这种实现方式简化了锁的操作,并保证了线程安全。

       研究 @synchronized 的实现细节时,我们发现它在对象上暗中添加了异常处理。当同步对象时抛出异常,锁会被自动释放。同时,@synchronized 结构在工作时为传入对象分配了一个递归锁。在代码中,我们观察到它如何实现锁的分配、释放以及处理 nil 的情况。

       通过阅读源码,我们了解到 @synchronized 结构如何将锁与对象关联,并在同步过程中处理内存地址哈希、链表操作、锁的加锁与解锁等关键步骤。它通过递归锁机制确保同一线程多次获取锁时不会造成死锁。

       在实际应用中,@synchronized 结构通过函数 objc_sync_enter 和 objc_sync_exit 实现锁的管理。当对象在 @synchronized block 中被释放或设为 nil 时,系统能够正确处理并避免潜在的竞态条件(race conditions),确保线程安全。

       总结来说,@synchronized 结构通过简化锁的操作、分配递归锁以及处理内存管理细节,为 Objective-C 程序提供了高效的线程安全机制。研究其实现有助于深入理解并发编程中锁的概念与应用,进一步提升程序的可靠性和性能。

synchronize底层原理

       synchronize底层原理是什么?我们先通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的:

       1 package com.paddx.test.concurrent;

       2

       3 public class SynchronizedDemo {

       4 public void method() {

       5 synchronized (this) {

       6 System.out.println(Method 1 start);

       7 }

       8 }

       9 }

       反编译结果:

       关于这两条指令的作用,我们直接参考JVM规范中描述:

       monitorenter :

       Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

        If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.

        If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.

        If another thread already owns the monitor associated with objectref, the thread blocks until the monitors entry count is zero, then tries again to gain ownership.

       这段话的大概意思为:

       每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程:

       1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

       2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

       3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

       monitorexit:

       The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

       The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

       这段话的大概意思为:

       执行monitorexit的线程必须是objectref所对应的monitor的所有者。

       指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

       通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

       我们再来看一下同步方法的反编译结果:

       源代码:

       1 package com.paddx.test.concurrent;

       2

       3 public class SynchronizedMethod {

       4 public synchronized void method() {

       5 System.out.println(Hello World!);

       6 }

       7 }

       反编译结果:

       从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。