1.MarkWord和Synchronized的源码锁升级机制详解(JDK8)
2.08.从源码揭秘偏向锁的升级
3.synchronize底层原理
4.7个连环问题揭开java多线程背后的核心原理!
5.synchronized和ReentrantLock的源码5个区别!
6.Java之Unsafe-越迷人的源码越危险
MarkWord和Synchronized的锁升级机制详解(JDK8)
锁升级机制在JDK 后已经废弃,本文所述仅为面试中常问的源码低版本synchronized的锁升级机制,具体新机制需查阅最新JDK源码。源码
在Java并发编程中,源码wxauto 源码解析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之后,引入了偏向锁与轻量锁来减小获取和释放锁的性能消耗。
锁升级分为四种状态:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁,锁会随着线程的竞争情况逐渐升级,但锁升级是不可逆的。
偏向锁在没有其他线程竞争时,持有偏向锁的线程不会主动释放,偏向锁的释放时机是在其他线程竞争该锁时。
轻量级锁使用CAS操作,尝试将对象头部的锁记录指针替换为指向线程栈上的锁记录。轻量级锁的撤销意味着不再通过自旋的方式等待获取锁,而是直接阻塞线程。
重量级锁状态下,对象的头部会指向一个Monitor对象,该Monitor对象负责管理锁的获取和释放。
JDK 1.6及之后版本引入了自适应自旋锁、锁消除和锁粗化等锁优化策略,以进一步提升synchronized的性能。
自适应自旋锁根据前一次在相同锁上的自旋时间以及锁的持有者状态来动态决定自旋的上限次数。
锁消除是JVM在JIT编译期间进行的优化,通过逃逸分析来消除不可能存在共享资源竞争的锁。
锁粗化是通过将加锁范围扩展到整个操作序列的外部,降低加锁解锁的频率来减少性能损耗。
本文总结了JDK8中synchronized的锁升级机制,介绍了无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁的升级流程,以提升并发效率。
.从源码揭秘偏向锁的升级
深入探讨偏向锁的升级至轻量级锁的过程,主要涉及HotSpot虚拟机的源码分析。在学习synchronized机制时,将通过本篇文章解答关于synchronized功能的相关问题。首先,进行一些准备工作,了解在分析synchronized源码前的必要步骤。然后,通过示例代码的编译结果,揭示synchronized修饰代码块后生成的字节码指令,以及这些指令对应的操作。进一步地,使用jol工具跟踪对象状态,提供更直观的数据支持。
接下来,重点解析monitorenter指令的5元夺宝源码执行过程,包括其与templateTable_x和interp_masm_x方法之间的关联。通过分析注释中的参数设置,可以理解偏向锁升级为重量级锁的逻辑,以及epoch在偏向锁有效性判断中的作用。进一步,详细介绍对象头(markOop)的结构和其在偏向锁实现中的具体功能,包括epoch的含义及其在更新过程中的角色。
在理解了偏向锁的原理后,将分析其在不同条件下的执行流程,包括是否可偏向、是否重入偏向、是否依旧可偏向、epoch是否过期以及重新偏向等分支逻辑。接着,介绍偏向锁撤销和重偏向的过程,以及在获取偏向锁失败后的操作,即执行轻量级锁加锁的过程。最后,讨论偏向锁与轻量级锁的区别,总结它们的关键技术和性能特点,并简述偏向锁的争议与现状。
在偏向锁的实现中,关键点在于CAS操作的使用,以及在CAS竞争失败时导致的锁升级。偏向锁适用于单线程执行的场景,但在线程交替持有执行时,撤销和重偏向逻辑的复杂性导致性能下降,因此引入轻量级锁以保证“轻微”竞争情况的安全性。尽管偏向锁在Java 中已被弃用,但在当前广泛应用的Java 8环境下,了解偏向锁的原理仍然具有重要意义。
总结而言,偏向锁与轻量级锁分别针对不同场景进行了优化,它们的核心逻辑基于CAS操作,但在处理线程竞争时的表现有所不同。通过深入学习这两种锁的升级过程,可以更好地理解synchronized机制在Java并发编程中的应用。
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,vue群控源码然后将进入数设置为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对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
7个连环问题揭开java多线程背后的核心原理!
摘要:很多java入门新人一想到java多线程, 就会觉得很晕很绕,什么可见不可见的,也不了解为什么sync怎么就锁住了代码。很多java入门新人一想到java多线程, 就会觉得很晕很绕,神途源码哪有什么可见不可见的,也不了解为什么sync怎么就锁住了代码。
因此我在这里会提多个问题,如果能很好地回答这些问题,那么算是你对java多线程的原理有了一些了解,也可以借此学习一下这背后的核心原理。
Q: java中的主内存和工作内存是指什么?
A:java中, 主内存中的对象引用会被拷贝到各线程的工作内存中, 同时线程对变量的修改也会反馈到主内存中。
主内存对应于java堆中的对象实例部分(物理硬件的内存)
工作内存对应于虚拟机栈中的部分区域( 寄存器,高速缓存)
工作内存中是拷贝的工作副本
拷贝副本时,不会吧整个超级大的对象拷贝过来, 可能只是其中的某个基本数据类型或者引用。
因此我们知道各线程使用内存数据时,其实是有主内存和工作内存之分的。并不是一定每次都从同一个内存里取数据。
或者理解为大家使用数据时之间有一个缓存。
Q: 多线程不可见问题的原因是什么?
A:这里先讲一下虚拟机定义的内存原子操作:
lock: 用于主内存, 把变量标识为一条线程独占的状态
unlock : 主内存, 把锁定状态的变量释放
read: 读取, 从主内存读到工作线程中
load: 把read后的值放入到 工作副本中
use: 使用工作内存变量, 传给工作引擎
assign赋值: 把工作引擎的值传给工作内存变量
store: 工作内存中的变量传到主内存
write: 把值写入到主内存的变量中
根据这些指令,看一下面这个图, 然后再看之后的流程解释,就好理解了。
read和load、store、write是按顺序执行的, 但是中间可插入其他的操作。不可单独出现。
assgin之后, 会同步后主内存。即只有发生过assgin,才会做工作内存同步到主内存的操作。
新变量只能在主内存中产生
工作内存中使用某个变量副本时,必须先经历过assign或者load操作。 不可read后马上就use
lock操作可以被同一个线程执行多次,但相应地解锁也需要多次。
执行lock时,会清空工作内存中该变量的值。 清空后如果要使用,必须重新做load或者assign操作
unlock时,需要先把数据同步回主内存,再释放。
因此多线程普通变量的读取和写入操作存在并发问题, 主要在于2点:
只有assgin时, 才会更新主内存, 但由于指令重排序的情况,导致有时候某个assine指令先执行,然后这个提前被改变的变量就被其他线程拿走了,以至于其他线程无法及时看到更新后的内存值。
assgin时从工作内存到主内存之间,可能存在延迟,同样会导致数据被提前取走存到工作线程中。
Q: 那么volatile关键字为什么就可以实现可见性?可见性就是并发修改某个值后,这个值的修改对其他线程是马上可见的。
A: java内存模型堆volatile定义了以下特殊规则:
当一个线程修改了该变量的值时,会先lock住主存, 再立刻把新数据同步回内存。
使用该值时,其他工作内存都要从主内存中刷新!
这个期间会禁止对于该变量的指令重排序
禁止指令重排序的原理是在给volatile变量赋值时,会加1个lock动作, 而前面规定的内存模型原理中, lock之后才能做load或者assine,因此形成了1个内存屏障。
Q: 上面提到lock后会限制各工作内存要刷新主存的值load进来后才能用, 这个在底层是怎么实现的?
A:利用了cpu的总线锁+ 缓存一致性+ 嗅探机制实现, 属于计算机组成原理部分的知识。
这也就是为什么violate变量不能设置太多,如果设置太多,可能会引发总线风暴,造成cpu嗅探的成本大大增加。
Q: 那给方法加上synchronized关键字的原理是什么?和volatie的区别是啥?
A:
synchronized的重量级锁是通过对象内部的监视器(monitor)实现
monitor的线程互斥就是通过操作系统的mutex互斥锁实现的,而操作系统实现线程之间的切换需要从用户态到内核态的切换,所以切换成本非常高。
每个对象都持有一个moniter对象
具体流程如下:
首先,class文件的方法表结构中有个访问标志access_flags, 设置ACC_SYNCHRONIZED标志来表示被设置过synchronized。
线程在执行方法前先判断access_flags是否标记ACC_SYNCHRONIZED,如果标记则在执行方法前先去获取monitor对象。
获取成功则执行方法代码且执行完毕后释放monitor对象
如果获取失败则表示monitor对象被其他线程获取从而阻塞当前线程
注意,如果是sync{ }代码块,则是通过在代码中添加monitorEnter和monitorExit指令来实现获取和退出操作的。
如果对C语言有了解的,可以看看这个大哥些的文章Java精通并发-通过openjdk源码分析ObjectMonitor底层实现
Q: synchronized每次加锁解锁需要切换内核态和用户态, jvm是否有对这个过程做过一些优化?
A:jdk1.6之后, 引入了锁升级的概念,而这个锁升级就是针对sync关键字的
锁的状态总共有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁
四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,只能进行锁升级(从低级别到高级别),不能锁降级(高级别到低级别)
因此sync关键字不是一开始就直接使用很耗时的同步。而是一步步按照情况做升级
当对象刚建立,不存在锁竞争的时候, 每次进入同步方法/代码块会直接使用偏向锁
偏向锁原理: 每次尝试在对象头里设置当前使用这个对象的线程id, 只做一次,如果成功了就设置好threadId, 只要没有出现新的thread访问且markWord被修改,那么久)
2. 当发现对象头的线程id要被修改时,说明存在竞争时。升级为轻量级锁
轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话,采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效的。 CAS的对象是对象头的Mark Word, 此时仍然不会去调系统底层的方法做阻塞。
3. 但是如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重,这时候就会升级为重量级锁,也就是上面那个问题中提到的操作。
Q: 锁只可以升级不可以降级, 确定是都不能降级吗?
A:有可能被降级, 不可能存在共享资源竞争的锁。java存在一个运行期优化的功能需要开启server模式外加+DoEscapeAnalysis表示开启逃逸分析。
如果运行过程中检测到共享变量确定不会逃逸,则直接在编译层面去掉锁
举例:StringBuffer.append().append()
例如如果发现stringBuffer不会逃逸,则就会去掉这里append所携带的同步
而这种情况肯定只能发生在偏向锁上, 所以偏向锁可以被重置为无锁状态。
本文分享自华为云社区,作者:breakDraw。
synchronized和ReentrantLock的5个区别!
在Java中,尽管synchronized(内置锁)和ReentrantLock(可重入锁)都具备锁定和同步的功能,但它们在使用方式、操作细节和特性上存在显著差异。以下是对两者五个主要区别的简要阐述:区别1:用法
synchronized适用于方法、静态方法和代码块,而ReentrantLock仅限于代码块。synchronized的锁操作更为简洁,而ReentrantLock则需要手动创建和管理锁。区别2:加锁与释放
synchronized自动进行加锁和释放,而ReentrantLock则需要显式调用lock和unlock方法。ReentrantLock的解锁操作需确保在finally块中执行,以防止资源泄漏。区别3:锁类型
synchronized是公平锁,ReentrantLock则可选择公平或非公平。默认ReentrantLock是非公平锁,通过new ReentrantLock(true)可创建公平锁。区别4:中断响应
ReentrantLock支持lockInterruptibly,允许中断并释放锁,解决死锁问题。synchronized则不会响应中断,可能导致线程阻塞。区别5:底层实现
synchronized基于JVM的监视器(Monitor),字节码中表现为monitorenter和monitorexit。ReentrantLock则通过AQS(AbstractQueuedSynchronizer)API实现,源码更易理解和定制。 总结来说,synchronized和ReentrantLock在使用时需根据具体需求选择,它们的差异体现在控制的灵活性、中断响应和底层实现等方面。Java之Unsafe-越迷人的越危险
简要介绍:
Java语言先比较与C和C++有一个非常大的不同点在于Java语言无法直接操作内存,实际开发中,默认都是由JVM来进行内存分配和垃圾回收,而JVM在进行垃圾回收的时候,绝大多数垃圾回收器都需要STW(stop the world)这个问题往往会导致服务短暂或者较长时间的暂停。因此Unsafe提供了通过Java直接操作内存的API,尽管Unsafe是JavaNIO和并发的核心类,但是其如其名,这是一个官方不推荐开发者使用的及其不安全的类!
主要作用:序号作用API1内存管理。(包括分配内存、释放内存等。)allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)2非常规的对象实例化allocateInstance()方法提供了另一种创建实例的途径3操作类、对象、变量staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)4数组操作arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法5多线程同步。包括锁机制、CAS操作等monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap6挂起与恢复park、unpark7内存屏障loadFence、storeFence、fullFence一、获取Unsafe源码-基于jdk1.8/**在Unsafe源码中限制了获取Unsafe的ClassLoader,如果这个方法调用实例不是由BootStrap类加载器加载的,则会报错*因此,我们如果需要使用Unsafe类,可以通过反射的方式来获取。*/@CallerSensitivepublicstaticUnsafegetUnsafe(){ Classvar0=Reflection.getCallerClass();//此处会判断ClassLoader是否为空,BootStrap由C语言编写,在Java中获取会返回null。if(!VM.isSystemDomainLoader(var0.getClassLoader())){ thrownewSecurityException("Unsafe");}else{ returntheUnsafe;}}获取方式/***反射获取Unsafe**@returnUnsafe*/publicstaticfinalUnsafegetUnsafe(){ Unsafeunsafe=null;try{ FieldtheUnsafe=Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);unsafe=(Unsafe)theUnsafe.get(null);}catch(NoSuchFieldException|IllegalAccessExceptione){ e.printStackTrace();}returnunsafe;}二、操作方法数组操作packagecom.liziba.unsafe;importsun.misc.Unsafe;/***<p>*操作数组示例*</p>**@Author:Liziba*@Date:/5/:*/publicclassOperateArrayExample{ /***1、publicnativeintarrayBaseOffset(Class<?>var1);获取数组第一个元素的偏移地址*2、publicnativeintarrayIndexScale(Class<?>var1);获取数组中元素的增量地址*3、publicObjectgetObject(Objectvar1,intvar2);通过对象和地址偏移量获取元素*/publicstaticvoidoperateArrayUseUnsafe(){ //测试数组String[]exampleArray=newString[]{ "李","子","捌"};Unsafeunsafe=UnsafeFactory.getUnsafe();//获取数组的基本偏移量intbaseOffset=unsafe.arrayBaseOffset(String[].class);System.out.println("String[]baseoffsetis:"+baseOffset);//获取数组中元素的增量地址intscale=unsafe.arrayIndexScale(String[].class);System.out.println("String[]indexscaleis:"+scale);//获取数组中第n个元素i=(baseOffset+(scale*n-1))System.out.println("thirdelementis:"+unsafe.getObject(exampleArray,baseOffset+(scale*2)));//修改数组中第n个元素i=(baseOffset+(scale*n-1))unsafe.putObject(exampleArray,baseOffset+scale*2,"柒");System.out.println("thirdelementis:"+unsafe.getObject(exampleArray,baseOffset+(scale*2)));}publicstaticvoidmain(String[]args){ OperateArrayExample.operateArrayUseUnsafe();}}输出结果
对象操作packagecom.liziba.unsafe;importcom.liziba.unsafe.pojo.User;importsun.misc.Unsafe;importjava.io.File;importjava.io.FileInputStream;importjava.lang.reflect.Constructor;importjava.lang.reflect.Field;/***<p>*操作对象示例*</p>**@Author:Liziba*@Date:/5/:*/publicclassOperateObjectExample{ /***1、publicnativeObjectallocateInstance(Class<?>var1);分配内存*2、publicnativeClass<?>defineClass(Stringvar1,byte[]var2,intvar3,intvar4,ClassLoadervar5,ProtectionDomainvar6);方法定义一个类用于动态的创建类*@throwsException*/publicstaticvoidoperateObjectUseUnsafe()throwsException{ Unsafeunsafe=UnsafeFactory.getUnsafe();//使用Unsafe的allocateInstance()方法,可以无需使用构造函数的情况下实例化对象Useruser=(User)unsafe.allocateInstance(User.class);user.setId(1);user.setName("李子捌");System.out.println(user);//返回对象成员属性在内存中相对于对象在内存中地址的偏移量Fieldname=User.class.getDeclaredField("name");longfieldOffset=unsafe.objectFieldOffset(name);//使用Unsafe的putXxx()方法,可以直接修改内存地址指向的数据(可以越过权限访问控制符)unsafe.putObject(user,fieldOffset,"李子柒");System.out.println(user);//使用Unsafe在运行时通过.class文件,创建类FileclassFile=newFile("E:\workspaceall\liziba-javap5\out\production\liziba-javap5\com\liziba\unsafe\pojo\User.class");FileInputStreamfis=newFileInputStream(classFile);byte[]classContent=newbyte[(int)classFile.length()];fis.read(classContent);Class<?>clazz=unsafe.defineClass(null,classContent,0,classContent.length,null,null);Constructor<?>constructor=clazz.getDeclaredConstructor(int.class,String.class);System.out.println(constructor.newInstance(1,"李子玖"));}publicstaticvoidmain(String[]args){ try{ OperateObjectExample.operateObjectUseUnsafe();}catch(Exceptione){ e.printStackTrace();}}}输出结果
内存操作packagecom.liziba.unsafe;importsun.misc.Unsafe;/***<p>*内存地址操作示例*</p>**@Author:Liziba*@Date:/5/:*/publicclassOperateMemoryExample{ /***1、publicnativelongallocateMemory(longvar1);分配var1字节大小的内存,返回起始地址偏移量*2、publicnativelongreallocateMemory(longvar1,longvar3);重新给var1起始地址的内存分配长度为var3字节的内存,返回新的内存起始地址偏移量*3、publicnativevoidfreeMemory(longvar1);释放起始地址为var1的地址**分配地址的方法还有重分配,都是分配在堆外内存,返回的是一个long类型的地址偏移量。这个偏移量在Java程序中的每一块内存都是唯一的**/publicstaticvoidoperateMemoryUseUnsafe(){ Unsafeunsafe=UnsafeFactory.getUnsafe();//申请分配8byte的内存longaddress=unsafe.allocateMemory(1L);//初始化内存填充值unsafe.putByte(address,(byte)1);//测试输出System.out.println(newStringBuilder().append("address:").append(address).append("bytevalue:").append(unsafe.getByte(address)));//重新分配一个地址longnewAddress=unsafe.reallocateMemory(address,8L);unsafe.putLong(newAddress,L);System.out.println(newStringBuilder().append("address:").append(newAddress).append("longvalue:").append(unsafe.getLong(newAddress)));//释放地址,注意地址可能被其他使用unsafe.freeMemory(newAddress);System.out.println(newStringBuilder().append("address:").append(newAddress).append("longvalue:").append(unsafe.getLong(newAddress)));}publicstaticvoidmain(String[]args){ OperateMemoryExample.operateMemoryUseUnsafe();}}输出结果
CAS操作packagecom.liziba.unsafe;importcom.liziba.unsafe.pojo.User;importsun.misc.Unsafe;importjava.lang.reflect.Field;/***<p>*CAS操作示例*</p>**@Author:Liziba*@Date:/5/:*/publicclassOperateCASExample{ /***CAS==compareandswap(比较并替换)*当需要改变的值为期望值的时候,就替换为新的值,是原子(不可再分割)操作。Java中大量的并发框架底层使用到了CAS操作。*优势:无锁操作,减少线程切换带来的开销*缺点:CAS容易在并发的情况下失败从而引发性能问题,也存在ABA问题。**Unsafe中提供了三个方法*1、compareAndSwapInt*2、compareAndSwapLong*3、compareAndSwapObject**/publicstaticvoidoperateCASUseUnsafe()throwsException{ Useruser=newUser(1,"李子捌");System.out.println("preuservalue:"+user);Unsafeunsafe=UnsafeFactory.getUnsafe();Fieldid=user.getClass().getDeclaredField("id");Fieldname=user.getClass().getDeclaredField("name");//获取ID字段的内存偏移量longidFieldOffset=unsafe.objectFieldOffset(id);//获取name字段的内存偏移量longnameFieldOffset=unsafe.objectFieldOffset(name);//如果ID的期望值是1,则修改为successunsafe.compareAndSwapInt(user,idFieldOffset,1,);//如果name的期望值是小荔枝,则修改为李子柒failunsafe.compareAndSwapObject(user,nameFieldOffset,"小荔枝","李子柒");//输出修改的user对象System.out.println("postuservalue:"+user);}publicstaticvoidmain(String[]args){ try{ OperateCASExample.operateCASUseUnsafe();}catch(Exceptione){ e.printStackTrace();}}}输出结果
线程的挂起和恢复/***查看Java的java.util.concurrent.locks.LockSupport源代码可以发现LockSupport类*中有各种版本的pack方法但是最终都是通过调用Unsafe.park()方法实现的。*/publicclassLockSupport{ publicstaticvoidunpark(Threadthread){ if(thread!=null)UNSAFE.unpark(thread);}publicstaticvoidpark(Objectblocker){ Threadt=Thread.currentThread();setBlocker(t,blocker);UNSAFE.park(false,0L);setBlocker(t,null);}publicstaticvoidparkNanos(Objectblocker,longnanos){ if(nanos>0){ Threadt=Thread.currentThread();setBlocker(t,blocker);UNSAFE.park(false,nanos);setBlocker(t,null);}}publicstaticvoidparkNanos(longnanos){ if(nanos>0)UNSAFE.park(false,nanos);}publicstaticvoidparkUntil(Objectblocker,longdeadline){ Threadt=Thread.currentThread();setBlocker(t,blocker);UNSAFE.park(true,deadline);setBlocker(t,null);}publicstaticvoidparkUntil(longdeadline){ UNSAFE.park(true,deadline);}}我们平时如何实现浅克隆?
实现Closeable接口
重写close()方法
一、Unsafe实现浅克隆浅克隆工具类packagecom.liziba.unsafe.clone;importcom.liziba.unsafe.UnsafeFactory;importsun.misc.Unsafe;importjava.lang.reflect.Field;importjava.lang.reflect.Modifier;importjava.util.Arrays;/***<p>*浅克隆工具类*</p>**@Author:Liziba*@Date:/5/:*/publicclassShallowCloneUtil{ /***获取对象的内存地址**@Description*Unsafe类没有提供直接获取实例对象内存地址的方法,但是可以通过以下方式间接获取。*构建对象A,A包含了我们需要获取内存地址的B对象的引用,这样只有获取到A对象持有的B对象的引用地址,就可以知道B对象的地址了。*我们可以通过Unsafe类获取内存地址的方法publicnativelonggetLong(Objectvar1,longvar2)来获取;*此处我们为了方便,通过数组Object[]添加Object元素,持有Object的引用**@return*/publicstaticLonggetAddress(Objectobj){ Object[]objects=newObject[]{ obj};Unsafeunsafe=UnsafeFactory.getUnsafe();intarrayBaseOffset=unsafe.arrayBaseOffset(Object[].class);returnunsafe.getLong(objects,arrayBaseOffset);}/***获取对象的大小**@Dscription*Java中实例化一个对象时,JVM会在堆中分配非static的Field的内存,其他的static属性或者method在类加载期间或者JVM启动时已经存放在内存中。*所以我们计算对象的大小的时候只需要求和Field的大小就行了,JVM分配内存时,单个实例对象中的Field内存是连续不断地,*因此我们只需获取最大偏移量Filed的偏移量+最大偏移量Filed本身的大小即可**Java中基本数据类型所占的字节数*byte/boolean1字节*char/short2字节*int/float4字节*long/double8字节*boolean理论上占1/8字节,实际上按照1byte处理。*Java采用的是Unicode编码,每一个字节占8位,一个字节由8个二进制位组成。**@paramclazz*@return*/publicstaticLongsize(Classclazz){ //最后一个Filed的内存偏移量longmaxOffset=0;ClasslastFiledClass=null;Unsafeunsafe=UnsafeFactory.getUnsafe();do{ for(Fieldfield:clazz.getDeclaredFields()){ if(!Modifier.isStatic(field.getModifiers())){ longtmpOffset=unsafe.objectFieldOffset(field);if(tmpOffset>maxOffset){ maxOffset=tmpOffset;lastFiledClass=field.getType();}}}}while((clazz=clazz.getSuperclass())!=null);//最后一个Field本身的大小intlastFiledSize=(boolean.class.equals(lastFiledClass)||byte.class.equals(lastFiledClass))?1:(short.class.equals(lastFiledClass)||char.class.equals(lastFiledClass))?2:(int.class.equals(lastFiledClass)||float.class.equals(lastFiledClass))?4:8;returnmaxOffset+lastFiledSize;}/***申请一块固定大小的内存空间**@Description*通过Unsafe的publicnat