java高并发系列 - 第22天:JUC底层工具类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;

//用来记录网站访问量,每次访问+1

static int count;

//count在Demo.class对象中的地址偏移量

static long countOffset;

static {

try {

//获取Unsafe对象

Field field = Unsafe.class.getDeclaredField(“theUnsafe”);

field.setAccessible(true);

unsafe = (Unsafe) field.get(null);

Field countField = Demo2.class.getDeclaredField(“count”);

//获取count字段在Demo2中的内存地址的偏移量

countOffset = unsafe.staticFieldOffset(countField);

} catch (Exception e) {

e.printStackTrace();

}

}

//模拟访问一次

public static void request() throws InterruptedException {

//模拟耗时5毫秒

TimeUnit.MILLISECONDS.sleep(5);

//对count原子加1

unsafe.getAndAddInt(Demo2.class, countOffset, 1);

}

public static void main(String[] args) throws InterruptedException {

long starTime = System.currentTimeMillis();

int threadSize = 100;

CountDownLatch countDownLatch = new CountDownLatch(threadSize);

for (int i = 0; i < threadSize; i++) {

Thread thread = new Thread(() -> {

try {

for (int j = 0; j < 10; j++) {

request();

}

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

countDownLatch.countDown();

}

});

thread.start();

}

countDownLatch.await();

long endTime = System.currentTimeMillis();

System.out.println(Thread.currentThread().getName() + “,耗时:” + (endTime - starTime) + “,count=” + count);

}

}

输出:

main,耗时:114,count=1000

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

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

Unsafe中线程调度相关方法


这部分,包括线程挂起、恢复、锁机制等方法。

//取消阻塞线程

public native void unpark(Object thread);

//阻塞线程,isAbsolute:是否是绝对时间,如果为true,time是一个绝对时间,如果为false,time是一个相对时间,time表示纳秒

public native void park(boolean isAbsolute, long time);

//获得对象锁(可重入锁)

@Deprecated

public native void monitorEnter(Object o);

//释放对象锁

@Deprecated

public native void monitorExit(Object o);

//尝试获取对象锁

@Deprecated

public native boolean tryMonitorEnter(Object o);

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

park和unpark示例


代码如下:

package com.itsoku.chat21;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

import java.util.concurrent.TimeUnit;

/**

* 跟着阿里p7学并发,微信公众号:javacode2018

*/

public class Demo3 {

static Unsafe unsafe;

static {

try {

Field field = Unsafe.class.getDeclaredField(“theUnsafe”);

field.setAccessible(true);

unsafe = (Unsafe) field.get(null);

} catch (Exception e) {

e.printStackTrace();

}

}

/**

* 调用park和unpark,模拟线程的挂起和唤醒

* @throws InterruptedException

*/

public static void m1() throws InterruptedException {

Thread thread = new Thread(() -> {

System.out.println(System.currentTimeMillis() + “,” + Thread.currentThread().getName() + “,start”);

unsafe.park(false, 0);

System.out.println(System.currentTimeMillis() + “,” + Thread.currentThread().getName() + “,end”);

});

thread.setName(“thread1”);

thread.start();

TimeUnit.SECONDS.sleep(5);

unsafe.unpark(thread);

}

/**

* 阻塞指定的时间

*/

public static void m2() {

Thread thread = new Thread(() -> {

System.out.println(System.currentTimeMillis() + “,” + Thread.currentThread().getName() + “,start”);

//线程挂起3秒

unsafe.park(false, TimeUnit.SECONDS.toNanos(3));

System.out.println(System.currentTimeMillis() + “,” + Thread.currentThread().getName() + “,end”);

});

thread.setName(“thread2”);

thread.start();

}

public static void main(String[] args) throws InterruptedException {

m1();

m2();

}

}

输出:

1565000238474,thread1,start

1565000243475,thread1,end

1565000243475,thread2,start

1565000246476,thread2,end

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

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

juc中的LockSupport类是通过unpark和park方法实现的,需要了解LockSupport可以移步:JUC中的LockSupport工具类

Unsafe锁示例


代码如下:

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 Demo4 {

static Unsafe unsafe;

//用来记录网站访问量,每次访问+1

static int count;

static {

try {

Field field = Unsafe.class.getDeclaredField(“theUnsafe”);

field.setAccessible(true);

unsafe = (Unsafe) field.get(null);

} catch (Exception e) {

e.printStackTrace();

}

}

//模拟访问一次

public static void request() {

unsafe.monitorEnter(Demo4.class);

try {

count++;

} finally {

unsafe.monitorExit(Demo4.class);

}

}

public static void main(String[] args) throws InterruptedException {

long starTime = System.currentTimeMillis();

int threadSize = 100;

CountDownLatch countDownLatch = new CountDownLatch(threadSize);

for (int i = 0; i < threadSize; i++) {

Thread thread = new Thread(() -> {

try {

for (int j = 0; j < 10; j++) {

request();

}

} finally {

countDownLatch.countDown();

}

});

thread.start();

}

countDownLatch.await();

long endTime = System.currentTimeMillis();

System.out.println(Thread.currentThread().getName() + “,耗时:” + (endTime - starTime) + “,count=” + count);

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

2021年Java中高级面试必备知识点总结

在这个部分总结了2019年到目前为止Java常见面试问题,取其面试核心编写成这份文档笔记,从中分析面试官的心理,摸清面试官的“套路”,可以说搞定90%以上的Java中高级面试没一点难度。

本节总结的内容涵盖了:消息队列、Redis缓存、分库分表、读写分离、设计高并发系统、分布式系统、高可用系统、SpringCloud微服务架构等一系列互联网主流高级技术的知识点。

目录:

(上述只是一个整体目录大纲,每个点里面都有如下所示的详细内容,从面试问题——分析面试官心理——剖析面试题——完美解答的一个过程)

部分内容:

对于每一个做技术的来说,学习是不能停止的,小编把2019年到目前为止Java的核心知识提炼出来了,无论你现在是处于什么阶段,如你所见,这份文档的内容无论是对于你找面试工作还是提升技术广度深度都是完美的。

不想被后浪淘汰的话,赶紧搞起来吧,高清完整版一共是888页,需要的话可以点赞+关注
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />

2021年Java中高级面试必备知识点总结

在这个部分总结了2019年到目前为止Java常见面试问题,取其面试核心编写成这份文档笔记,从中分析面试官的心理,摸清面试官的“套路”,可以说搞定90%以上的Java中高级面试没一点难度。

本节总结的内容涵盖了:消息队列、Redis缓存、分库分表、读写分离、设计高并发系统、分布式系统、高可用系统、SpringCloud微服务架构等一系列互联网主流高级技术的知识点。

目录:

[外链图片转存中…(img-qHxUvav1-1713499107859)]

(上述只是一个整体目录大纲,每个点里面都有如下所示的详细内容,从面试问题——分析面试官心理——剖析面试题——完美解答的一个过程)

[外链图片转存中…(img-YohtJdsA-1713499107860)]

部分内容:

[外链图片转存中…(img-H4xsBJUd-1713499107861)]

[外链图片转存中…(img-CDYDGomN-1713499107862)]

[外链图片转存中…(img-DoN2OKq2-1713499107862)]

对于每一个做技术的来说,学习是不能停止的,小编把2019年到目前为止Java的核心知识提炼出来了,无论你现在是处于什么阶段,如你所见,这份文档的内容无论是对于你找面试工作还是提升技术广度深度都是完美的。

不想被后浪淘汰的话,赶紧搞起来吧,高清完整版一共是888页,需要的话可以点赞+关注
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值