热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java高并发系列第22天:JUC底层工具类Unsafe,高手必须要了解

这是java高并发系列第22篇文章,文章基于jdk1.8环境。本文主要内容Unsafe基本介绍获取Unsafe实例Unsafe中的CAS操作Unsafe中原子操作相关

这是java高并发系列第22篇文章,文章基于jdk1.8环境。

本文主要内容

  1. Unsafe基本介绍

  2. 获取Unsafe实例

  3. Unsafe中的CAS操作

  4. Unsafe中原子操作相关方法介绍

  5. Unsafe中线程调度相关方法介绍

  6. park和unpark示例

  7. Unsafe锁示例

  8. Unsafe中对volatile的支持

基本介绍

最近我们一直在学习java高并发,java高并发中主要涉及到类位于java.util.concurrent包中,简称juc,juc中大部分类都是依赖于Unsafe来实现的,主要用到了Unsafe中的CAS、线程挂起、线程恢复等相关功能。所以如果打算深入了解JUC原理的,必须先了解一下Unsafe类。

先上一幅Unsafe类的功能图:

640?wx_fmt=png

Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。

从Unsafe功能图上看出,Unsafe提供的API大致可分为内存操作CASClass相关对象操作线程调度系统信息获取内存屏障数组操作等几类,本文主要介绍3个常用的操作:CAS、线程调度、对象操作。

看一下UnSafe的原码部分:

public final class Unsafe {// 单例对象private static final Unsafe theUnsafe;private Unsafe() {}@CallerSensitivepublic static Unsafe getUnsafe() {Class var0 = Reflection.getCallerClass();// 仅在引导类加载器`BootstrapClassLoader`加载时才合法if(!VM.isSystemDomainLoader(var0.getClassLoader())) {    throw new SecurityException("Unsafe");} else {return theUnsafe;}}
}

从代码中可以看出,Unsafe类为单例实现,提供静态方法getUnsafe获取Unsafe实例,内部会判断当前调用者是否是由系统类加载器加载的,如果不是系统类加载器加载的,会抛出SecurityException异常。

那我们想使用这个类,如何获取呢?

可以把我们的类放在jdk的lib目录下,那么启动的时候会自动加载,这种方式不是很好。

我们学过反射,通过反射可以获取到Unsafe中的theUnsafe字段的值,这样可以获取到Unsafe对象的实例。

通过反射获取Unsafe实例

代码如下:

package com.itsoku.chat21;import sun.misc.Unsafe;import java.lang.reflect.Field;/*** 跟着阿里p7学并发,微信公众号:javacode2018*/
public class Demo1 {static Unsafe unsafe;static {try {Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);unsafe = (Unsafe) field.get(null);} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) {System.out.println(unsafe);}
}

输出:

sun.misc.Unsafe@76ed5528

Unsafe中的CAS操作

看一下Unsafe中CAS相关方法定义:

/*** CAS 操作** @param o        包含要修改field的对象* @param offset   对象中某field的偏移量* @param expected 期望值* @param update   更新值* @return true | false*/
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);

什么是CAS? 即比较并替换,实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作,多个线程同时执行cas操作,只有一个会成功。我们都知道,CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现的, 其实在这一点上还是有排他锁的,只是比起用synchronized, 这里的排他时间要短的多, 所以在多线程情况下性能会比较好。

说一下offset,offeset为字段的偏移量,每个对象有个地址,offset是字段相对于对象地址的偏移量,对象地址记为baseAddress,字段偏移量记为offeset,那么字段对应的实际地址就是baseAddress+offeset,所以cas通过对象、偏移量就可以去操作字段对应的值了。

CAS在java.util.concurrent.atomic相关类、Java AQS、JUC中并发集合等实现上有非常广泛的应用,我们看一下java.util.concurrent.atomic.AtomicInteger类,这个类可以在多线程环境中对int类型的数据执行高效的原子修改操作,并保证数据的正确性,看一下此类中用到Unsafe cas的地方:

640?wx_fmt=png

640?wx_fmt=png

JUC中其他地方使用到CAS的地方就不列举了,有兴趣的可以去看一下源码。

Unsafe中原子操作相关方法介绍

5个方法,看一下实现:

/*** int类型值原子操作,对var2地址对应的值做原子增加操作(增加var4)** @param var1 操作的对象* @param var2 var2字段内存地址偏移量* @param var4 需要加的值* @return*/
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;
}/*** long类型值原子操作,对var2地址对应的值做原子增加操作(增加var4)** @param var1 操作的对象* @param var2 var2字段内存地址偏移量* @param var4 需要加的值* @return 返回旧值*/
public final long getAndAddLong(Object var1, long var2, long var4) {long var6;do {var6 = this.getLongVolatile(var1, var2);} while (!this.compareAndSwapLong(var1, var2, var6, var6 + var4));return var6;
}/*** int类型值原子操作方法,将var2地址对应的值置为var4** @param var1 操作的对象* @param var2 var2字段内存地址偏移量* @param var4 新值* @return 返回旧值*/
public final int getAndSetInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while (!this.compareAndSwapInt(var1, var2, var5, var4));return var5;
}/*** long类型值原子操作方法,将var2地址对应的值置为var4** @param var1 操作的对象* @param var2 var2字段内存地址偏移量* @param var4 新值* @return 返回旧值*/
public final long getAndSetLong(Object var1, long var2, long var4) {long var6;do {var6 = this.getLongVolatile(var1, var2);} while (!this.compareAndSwapLong(var1, var2, var6, var4));return var6;
}/*** Object类型值原子操作方法,将var2地址对应的值置为var4** @param var1 操作的对象* @param var2 var2字段内存地址偏移量* @param var4 新值* @return 返回旧值*/
public final Object getAndSetObject(Object var1, long var2, Object var4) {Object var5;do {var5 = this.getObjectVolatile(var1, var2);} while (!this.compareAndSwapObject(var1, var2, var5, var4));return var5;
}

看一下上面的方法,内部通过自旋的CAS操作实现的,这些方法都可以保证操作的数据在多线程环境中的原子性,正确性。

来个示例,我们还是来实现一个网站计数功能,同时有100个人发起对网站的请求,每个人发起10次请求,每次请求算一次,最终结果是1000次,代码如下:

package com.itsoku.chat21;import sun.misc.Unsafe;import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;/*** 跟着阿里p7学并发,微信公众号:javacode2018*/
public class Demo2 {static Unsafe unsafe;//用来记录网站访问量&#xff0c;每次访问&#43;1static int count;//count在Demo.class对象中的地址偏移量static long countOffset;static {try {//获取Unsafe对象Field field &#61; Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);unsafe &#61; (Unsafe) field.get(null);Field countField &#61; Demo2.class.getDeclaredField("count");//获取count字段在Demo2中的内存地址的偏移量countOffset &#61; unsafe.staticFieldOffset(countField);} catch (Exception e) {e.printStackTrace();}}//模拟访问一次public static void request() throws InterruptedException {//模拟耗时5毫秒TimeUnit.MILLISECONDS.sleep(5);//对count原子加1unsafe.getAndAddInt(Demo2.class, countOffset, 1);}public static void main(String[] args) throws InterruptedException {long starTime &#61; System.currentTimeMillis();int threadSize &#61; 100;CountDownLatch countDownLatch &#61; new CountDownLatch(threadSize);for (int i &#61; 0; i < threadSize; i&#43;&#43;) {Thread thread &#61; new Thread(() -> {try {for (int j &#61; 0; j < 10; j&#43;&#43;) {request();}} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}});thread.start();}countDownLatch.await();long endTime &#61; System.currentTimeMillis();System.out.println(Thread.currentThread().getName() &#43; "&#xff0c;耗时&#xff1a;" &#43; (endTime - starTime) &#43; ",count&#61;" &#43; count);}
}

输出&#xff1a;

main&#xff0c;耗时&#xff1a;114,count&#61;1000

代码中我们在静态块中通过反射获取到了Unsafe类的实例&#xff0c;然后获取Demo2中count字段内存地址偏移量countOffset&#xff0c;main方法中模拟了100个人&#xff0c;每人发起10次请求&#xff0c;等到所有请求完毕之后&#xff0c;输出count的结果。

代码中用到了CountDownLatch&#xff0c;通过countDownLatch.await()让主线程等待&#xff0c;等待100个子线程都执行完毕之后&#xff0c;主线程在进行运行。CountDownLatch的使用可以参考&#xff1a;JUC中等待多线程完成的工具类CountDownLatch

Unsafe中线程调度相关方法

这部分&#xff0c;包括线程挂起、恢复、锁机制等方法。

//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程,isAbsolute&#xff1a;是否是绝对时间&#xff0c;如果为true&#xff0c;time是一个绝对时间&#xff0c;如果为false&#xff0c;time是一个相对时间&#xff0c;time表示纳秒
public native void park(boolean isAbsolute, long time);
//获得对象锁&#xff08;可重入锁&#xff09;
&#64;Deprecated
public native void monitorEnter(Object o);
//释放对象锁
&#64;Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
&#64;Deprecated
public native boolean tryMonitorEnter(Object o);

调用park后&#xff0c;线程将被阻塞&#xff0c;直到unpark调用或者超时&#xff0c;如果之前调用过unpark,不会进行阻塞&#xff0c;即parkunpark不区分先后顺序。monitorEnter、monitorExit、tryMonitorEnter 3个方法已过期&#xff0c;不建议使用了。

park和unpark示例

代码如下&#xff1a;

package com.itsoku.chat21;import sun.misc.Unsafe;import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;/*** 跟着阿里p7学并发&#xff0c;微信公众号&#xff1a;javacode2018*/
public class Demo3 {static Unsafe unsafe;static {try {Field field &#61; Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);unsafe &#61; (Unsafe) field.get(null);} catch (Exception e) {e.printStackTrace();}}/*** 调用park和unpark&#xff0c;模拟线程的挂起和唤醒** &#64;throws InterruptedException*/public static void m1() throws InterruptedException {Thread thread &#61; new Thread(() -> {System.out.println(System.currentTimeMillis() &#43; "," &#43; Thread.currentThread().getName() &#43; ",start");unsafe.park(false, 0);System.out.println(System.currentTimeMillis() &#43; "," &#43; Thread.currentThread().getName() &#43; ",end");});thread.setName("thread1");thread.start();TimeUnit.SECONDS.sleep(5);unsafe.unpark(thread);}/*** 阻塞指定的时间*/public static void m2() {Thread thread &#61; new Thread(() -> {System.out.println(System.currentTimeMillis() &#43; "," &#43; Thread.currentThread().getName() &#43; ",start");//线程挂起3秒unsafe.park(false, TimeUnit.SECONDS.toNanos(3));System.out.println(System.currentTimeMillis() &#43; "," &#43; Thread.currentThread().getName() &#43; ",end");});thread.setName("thread2");thread.start();}public static void main(String[] args) throws InterruptedException {m1();m2();}
}

输出&#xff1a;

1565000238474,thread1,start
1565000243475,thread1,end
1565000243475,thread2,start
1565000246476,thread2,end

m1()中thread1调用park方法&#xff0c;park方法会将当前线程阻塞&#xff0c;被阻塞了5秒之后&#xff0c;被主线程调用unpark方法给唤醒了&#xff0c;unpark方法参数表示需要唤醒的线程。

线程中相当于有个许可&#xff0c;许可默认是0&#xff0c;调用park的时候&#xff0c;发现是0会阻塞当前线程&#xff0c;调用unpark之后&#xff0c;许可会被置为1&#xff0c;并会唤醒当前线程。如果在park之前先调用了unpark方法&#xff0c;执行park方法的时候&#xff0c;不会阻塞。park方法被唤醒之后&#xff0c;许可又会被置为0。多次调用unpark的效果是一样的&#xff0c;许可还是1。

juc中的LockSupport类是通过unpark和park方法实现的&#xff0c;需要了解LockSupport可以移步&#xff1a;JUC中的LockSupport工具类

Unsafe锁示例

代码如下&#xff1a;

package com.itsoku.chat21;import sun.misc.Unsafe;import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;/*** 跟着阿里p7学并发&#xff0c;微信公众号&#xff1a;javacode2018*/
public class Demo4 {static Unsafe unsafe;//用来记录网站访问量&#xff0c;每次访问&#43;1static int count;static {try {Field field &#61; Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);unsafe &#61; (Unsafe) field.get(null);} catch (Exception e) {e.printStackTrace();}}//模拟访问一次public static void request() {unsafe.monitorEnter(Demo4.class);try {count&#43;&#43;;} finally {unsafe.monitorExit(Demo4.class);}}public static void main(String[] args) throws InterruptedException {long starTime &#61; System.currentTimeMillis();int threadSize &#61; 100;CountDownLatch countDownLatch &#61; new CountDownLatch(threadSize);for (int i &#61; 0; i < threadSize; i&#43;&#43;) {Thread thread &#61; new Thread(() -> {try {for (int j &#61; 0; j < 10; j&#43;&#43;) {request();}} finally {countDownLatch.countDown();}});thread.start();}countDownLatch.await();long endTime &#61; System.currentTimeMillis();System.out.println(Thread.currentThread().getName() &#43; "&#xff0c;耗时&#xff1a;" &#43; (endTime - starTime) &#43; ",count&#61;" &#43; count);}
}

输出&#xff1a;

main&#xff0c;耗时&#xff1a;64,count&#61;1000

注意&#xff1a;

  1. monitorEnter、monitorExit、tryMonitorEnter 3个方法已过期&#xff0c;不建议使用了

  2. monitorEnter、monitorExit必须成对出现&#xff0c;出现的次数必须一致&#xff0c;也就是说锁了n次&#xff0c;也必须释放n次&#xff0c;否则会造成死锁

Unsafe中保证变量的可见性

关于变量可见性需要先了解java内存模型JMM&#xff0c;可以移步到&#xff1a;

JMM相关的一些概念

volatile与Java内存模型

java中操作内存分为主内存和工作内存&#xff0c;共享数据在主内存中&#xff0c;线程如果需要操作主内存的数据&#xff0c;需要先将主内存的数据复制到线程独有的工作内存中&#xff0c;操作完成之后再将其刷新到主内存中。如线程A要想看到线程B修改后的数据&#xff0c;需要满足&#xff1a;线程B修改数据之后&#xff0c;需要将数据从自己的工作内存中刷新到主内存中&#xff0c;并且A需要去主内存中读取数据。

被关键字volatile修饰的数据&#xff0c;有2点语义&#xff1a;

  1. 如果一个变量被volatile修饰&#xff0c;读取这个变量时候&#xff0c;会强制从主内存中读取&#xff0c;然后将其复制到当前线程的工作内存中使用

  2. 给volatile修饰的变量赋值的时候&#xff0c;会强制将赋值的结果从工作内存刷新到主内存

上面2点语义保证了被volatile修饰的数据在多线程中的可见性。

Unsafe中提供了和volatile语义一样的功能的方法&#xff0c;如下&#xff1a;

//设置给定对象的int值&#xff0c;使用volatile语义&#xff0c;即设置后立马更新到内存对其他线程可见
public native void  putIntVolatile(Object o, long offset, int x);
//获得给定对象的指定偏移量offset的int值&#xff0c;使用volatile语义&#xff0c;总能获取到最新的int值。
public native int getIntVolatile(Object o, long offset);

putIntVolatile方法&#xff0c;2个参数&#xff1a;

o&#xff1a;表示需要操作的对象

offset&#xff1a;表示操作对象中的某个字段地址偏移量

x&#xff1a;将offset对应的字段的值修改为x&#xff0c;并且立即刷新到主存中

调用这个方法&#xff0c;会强制将工作内存中修改的数据刷新到主内存中。

getIntVolatile方法&#xff0c;2个参数

o&#xff1a;表示需要操作的对象

offset&#xff1a;表示操作对象中的某个字段地址偏移量

每次调用这个方法都会强制从主内存读取值&#xff0c;将其复制到工作内存中使用。

其他的还有几个putXXXVolatile、getXXXVolatile方法和上面2个类似。

本文主要讲解这些内容&#xff0c;希望您能有所收获&#xff0c;谢谢。

java高并发系列目录

  1. 第1天:必须知道的几个概念

  2. 第2天:并发级别

  3. 第3天:有关并行的两个重要定律

  4. 第4天:JMM相关的一些概念

  5. 第5天:深入理解进程和线程

  6. 第6天:线程的基本操作

  7. 第7天:volatile与Java内存模型

  8. 第8天:线程组

  9. 第9天&#xff1a;用户线程和守护线程

  10. 第10天:线程安全和synchronized关键字

  11. 第11天:线程中断的几种方式

  12. 第12天JUC:ReentrantLock重入锁

  13. 第13天:JUC中的Condition对象

  14. 第14天:JUC中的LockSupport工具类&#xff0c;必备技能

  15. 第15天&#xff1a;JUC中的Semaphore&#xff08;信号量&#xff09;

  16. 第16天&#xff1a;JUC中等待多线程完成的工具类CountDownLatch

  17. 第17天&#xff1a;JUC中的循环栅栏CyclicBarrier的6种使用场景

  18. 18天&#xff1a;JAVA线程池&#xff0c;这一篇就够了

  19. 第19天&#xff1a;JUC中的Executor框架详解1

  20. 第20天&#xff1a;JUC中的Executor框架详解2

  21. 第21天&#xff1a;java中的CAS

java高并发系列连载中&#xff0c;总计估计会有四五十篇文章。

跟着阿里p7学并发&#xff0c;微信公众号&#xff1a;javacode2018

640?wx_fmt&#61;jpeg


推荐阅读
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了Java集合库的使用方法,包括如何方便地重复使用集合以及下溯造型的应用。通过使用集合库,可以方便地取用各种集合,并将其插入到自己的程序中。为了使集合能够重复使用,Java提供了一种通用类型,即Object类型。通过添加指向集合的对象句柄,可以实现对集合的重复使用。然而,由于集合只能容纳Object类型,当向集合中添加对象句柄时,会丢失其身份或标识信息。为了恢复其本来面貌,可以使用下溯造型。本文还介绍了Java 1.2集合库的特点和优势。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • Hibernate延迟加载深入分析-集合属性的延迟加载策略
    本文深入分析了Hibernate延迟加载的机制,特别是集合属性的延迟加载策略。通过延迟加载,可以降低系统的内存开销,提高Hibernate的运行性能。对于集合属性,推荐使用延迟加载策略,即在系统需要使用集合属性时才从数据库装载关联的数据,避免一次加载所有集合属性导致性能下降。 ... [详细]
  • 本文介绍了安全性要求高的真正密码随机数生成器的概念和原理。首先解释了统计学意义上的伪随机数和真随机数的区别,以及伪随机数在密码学安全中的应用。然后讨论了真随机数的定义和产生方法,并指出了实际情况下真随机数的不可预测性和复杂性。最后介绍了随机数生成器的概念和方法。 ... [详细]
  • JavaScript和HTML之间的交互是经由过程事宜完成的。事宜:文档或浏览器窗口中发作的一些特定的交互霎时。能够运用侦听器(或处置惩罚递次来预订事宜),以便事宜发作时实行相应的 ... [详细]
author-avatar
cresslyty_723
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有