IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【Java 线程 · 并发】LockSupport 线程控制 & Unsafe 核心类 -> 正文阅读

[Java知识库]【Java 线程 · 并发】LockSupport 线程控制 & Unsafe 核心类

一、概述

LockSupport类,JUC包 中的一个工具类,用来创建锁和其他同步类的基本线程阻塞原语。

Basic thread blocking primitives for creating locks and other synchronization classes

LockSupport类 的核心方法其实就两个:park()unpark(),其中 park() 方法用来阻塞当前调用线程,unpark() 方法用于唤醒指定线程。
这其实和 Object 类的 wait()signal() 方法有些类似,但是 LockSupport类 的这两种方法从语意上讲比 Object类 的方法更清晰,而且可以针对指定线程进行阻塞和唤醒。

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
初始时,permit为0,当调用unpark()方法时,线程的permit加1,当调用park()方法时,如果permit为0,则调用线程进入阻塞状态。

二、LockSupport 线程控制

1. 使用方法

LockSupport两个核心方法:

/* park */
public static void park() {
    U.park(false, 0L);
}
/* unpark */
public static void unpark(Thread thread) {
   if (thread != null)
        U.unpark(thread);
}

举例测试如何阻塞线程:

import java.util.concurrent.locks.LockSupport;

public class UseLockSupport {
	public static void main(String[] args) {
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("线程 " + Thread.currentThread().getName() + " 开始执行执行并进行park等待");
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				LockSupport.park();
				System.out.println("线程 " + Thread.currentThread().getName() + " 解除park等待继续执行");
			}
		});
		t.start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		LockSupport.unpark(t);
	}
}

运行结果:
运行结果

2. 底层实现

LockSupport 对线程的控制基于 Unsafe 类的 线程调度 功能:

/* park */
public static void park() {
    U.park(false, 0L);
}
/* unpark */
public static void unpark(Thread thread) {
   if (thread != null)
        U.unpark(thread);
}

三、Unsafe 核心类

Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力:

  • 不受 JVM 管理,也就意味着无法被 GC,需要我们手动 GC,稍有不慎就会出现内存泄漏。
  • Unsafe 部分方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量需程序员计算,若出现问题会导致整个JVM实例崩溃,应用程序也会随之崩溃
  • 直接操作内存,其速度更快,在高并发的条件下能很好地提高效率。

1. 创建对象

Unsafe类是final的,不允许继承。

在JDK中,创建 sun.misc.Unsafe 对象 需要调用 getUnsafe() 方法,它采用了单例模式,保证全局仅有一个对象:

/* sun.misc.Unsafe类部分代码 */
//私有的构造方法
private Unsafe() {}
//静态变量 (final单例)
private static final Unsafe theUnsafe = new Unsafe();
//获取对象的单例工厂
@CallerSensitive
public static Unsafe getUnsafe() {
	//获取当前类的ClassLoader加载器
	Class<?> caller = Reflection.getCallerClass();
	//caller 是否为 BootStrapLoader
	if (!VM.isSystemDomainLoader(caller.getClassLoader()))
		//否:抛出异常
		throw new SecurityException("Unsafe");
	//是:返回对象
	return theUnsafe;
}

获取 Unsafe 类对象的条件:当前类加载器为 BootStrapLoader,否则会抛出异常:
异常

为了获取到 sun.misc.Unsafe 对象,我们可以使用 反射 直接获取内部定义的单例:

/* 获取方法:(可能产生的异常:IllegalAccessException、NoSuchFieldException) */
//获取单例Field对象
Field f = Unsafe.class.getDeclaredField("theUnsafe");
//设置私有访问为true
f.setAccessible(true);
//获取对象
Unsafe unsafe = (Unsafe) f.get(null);

sun.misc.Unsafe 内部依赖于 jdk.internal.misc.Unsafe,但我们不能直接获取jdk.internal.misc.Unsafe 对象(包括反射,因为编译不通过)。

2. 内置方法

我们对方法的展示来自于 jdk.internal.misc.Unsafe

① 普通读写

通过 Unsafe 内置方法可以对任何对象进行读写,无论它私有与否:

/*
注解 @HotSpotIntrinsicCandidate:
	在 HotSpot 中有一套高效的实现,该高效实现基于CPU指令,运行时 HotSpot 维护的高效实现会替代JDK的源码实现,从而获得更高的效率。
*/
@HotSpotIntrinsicCandidate
public native int getInt(Object o, long offset);	//读方法,o代表需要操作内部属性的对象,而offset代表偏移量

@HotSpotIntrinsicCandidate
public native void putInt(Object o, long offset, int x);	//写方法,x代表想要写入的值

除此之外,还可以直接在地址上读写:

//从一个给定的内存地址获取本地指针,如果不是allocateMemory方法的,结果将不确定
@ForceInline
public long getAddress(Object o, long offset) {
	if (ADDRESS_SIZE == 4) {
		return Integer.toUnsignedLong(getInt(o, offset));
	} else {
		return getLong(o, offset);
	}
}

//存储一个本地指针到一个给定的内存地址,如果不是allocateMemory方法的,结果将不确定
@ForceInline
public void putAddress(Object o, long offset, long x) {
	if (ADDRESS_SIZE == 4) {
		putInt(o, offset, (int)x);
	} else {
		putLong(o, offset, x);
	}
}

除了基本数据类型以外,还提供了引用类型的读写方法:

// The following deprecated methods are used by JSR 166.
@Deprecated(since="12", forRemoval=true)
public final Object getObject(Object o, long offset) {
	return getReference(o, offset);
}

@Deprecated(since="12", forRemoval=true)
public final void putObject(Object o, long offset, Object x) {
	putReference(o, offset, x);
}

遗憾的是,在JDK12 以后,这些引用类型读写方法已经 “过时” 了,并不推荐使用,取而代之的是这些方法:

@HotSpotIntrinsicCandidate
public native Object getReference(Object o, long offset);

@HotSpotIntrinsicCandidate
public native void putReference(Object o, long offset, Object x);

② volatile 读写

普通的读写无法保证可见性有序性,而 volatile读写 方法则避免了该问题:
基本数据类型:

@HotSpotIntrinsicCandidate
public native void    putIntVolatile(Object o, long offset, int x);

@HotSpotIntrinsicCandidate
public native int     getIntVolatile(Object o, long offset);

引用类型:

@HotSpotIntrinsicCandidate
public native Object getReferenceVolatile(Object o, long offset);

@HotSpotIntrinsicCandidate
public native void putReferenceVolatile(Object o, long offset, Object x);

③ 有序写入

有序写入只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其他线程立马可见。

/* sun.misc.Unsafe */
@ForceInline
public void putOrderedInt(Object o, long offset, int x) {
    theInternalUnsafe.putIntRelease(o, offset, x);
}

而本方法依赖于方法,完成有序写入:

/* jdk.internal.misc.Unsafe */
@HotSpotIntrinsicCandidate
public final void putIntRelease(Object o, long offset, int x) {
    putIntVolatile(o, offset, x);
}

除此之外还有其他类型、引用类型对应的方法,在此不进行列举。

④ CAS 操作

JUC中大量运用了 CAS操作, CAS操作 是 JUC 的基础。Unsafe中提供了 多种类型 的CAS操作:

/* int CAS 操作 */
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
                                             int expected,
                                             int x);
}
/* Object CAS 操作 */
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetReference(Object o, long offset,
                                                   Object expected,
                                                   Object x);

⑤ 直接内存操作

Unsafe 类为我们提供了直接操作内存的能力,并且这些方法依赖于一些 native 本地方法,为了区分,本地方法以 数字 0 结尾。

分配内存

/* 分配内存 */
public long allocateMemory(long bytes) {
    bytes = alignToHeapWordSize(bytes);
    //分配内存检查
    allocateMemoryChecks(bytes);
    if (bytes == 0) {
        return 0;
    }
    //为其分配内存
    long p = allocateMemory0(bytes);
    //无法分配内存,抛出异常
    if (p == 0) {
        throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes");
    }
    //返回分配结果
    return p;
}

重新分配内存

/* 重新分配内存 */
public long reallocateMemory(long address, long bytes) {
    bytes = alignToHeapWordSize(bytes);
    //重新分配内存检查
    reallocateMemoryChecks(address, bytes);
    if (bytes == 0) {
        freeMemory(address);
        return 0;
    }
    //检查:分配新内存 OR 重新分配内存
    long p = (address == 0) ? allocateMemory0(bytes) : reallocateMemory0(address, bytes);
    //无法分配内存,抛出异常
    if (p == 0) {
        throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes");
    }
    //返回分配结果
    return p;
}

内存初始化

/* 内存初始化 */
public void setMemory(Object o, long offset, long bytes, byte value) {
    setMemoryChecks(o, offset, bytes, value);	//内存初始化检查
    if (bytes == 0) {
        return;
    }
    setMemory0(o, offset, bytes, value);		//内存初始化
}

内存复制

/* 内存复制 */
public void copyMemory(Object srcBase, long srcOffset,
                           Object destBase, long destOffset,
                           long bytes) {
    copyMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes);	//内存复制检查
    if (bytes == 0) {
        return;
    }
    copyMemory0(srcBase, srcOffset, destBase, destOffset, bytes);	//内存复制
}

清除内存

/* 清除内存 */
public void freeMemory(long address) {
    freeMemoryChecks(address);	//清除内存检查
    if (address == 0) {			//检查地址
        return;
    }
    freeMemory0(address);		//清除内存
}

⑥ 线程调度

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

@HotSpotIntrinsicCandidate
public native void unpark(Object thread);

⑦ 内存屏障

  • loadFence:保证在这个屏障之前的所有 读操作 都已经完成。
  • storeFence:保证在这个屏障之前的所有 写操作 都已经完成。
  • fullFence:保证在这个屏障之前的所有 读写操作 都已经完成。
@HotSpotIntrinsicCandidate
public native void loadFence();

@HotSpotIntrinsicCandidate
public native void storeFence();

@HotSpotIntrinsicCandidate
public native void fullFence();

3. 特殊应用

① 无视构造方法

public static void main(String[] args) throws Exception {
    Unsafe unsafe = null;
    try{
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        unsafe = (Unsafe) f.get(null);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    }
    A o1 = new A(); // 构造方法
    o1.a(); // prints 1
    A o2 = A.class.getDeclaredConstructor().newInstance();	//反射
    o2.a(); // prints 1
    A o3 = (A) unsafe.allocateInstance(A.class); // Unsafe.allocateInstance()
    o3.a(); // prints 0
}
static class A {
    private long a; // 未经构造方法初始化, 默认为 0
    public A() {
        this.a = 1; // initialization
    }
    public long a() {
        System.out.println(this.a);
            return this.a;
    }
}

运行结果:
结果

② 巨型数组

//定义unsafe对象
private static Unsafe unsafe;
public static void main(String[] args) throws Exception {
	/* 获取对象 */
    Field f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    unsafe = (Unsafe) f.get(null);
    // 设置数组大小为Integer.MAX_VALUE的2倍
    long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
    SuperArray array = new SuperArray(SUPER_SIZE);
    System.out.println("Array size:" + array.size()); // 4294967294
    int sum = 0;
    for (int i = 0; i < 100; i++) {
        array.set((long) Integer.MAX_VALUE + i, (byte) 3);
        sum += array.get((long) Integer.MAX_VALUE + i);
    }
    System.out.println("Sum of 100 elements:" + sum);  // 总数应为300
}

private static Unsafe getUnsafe() {
    return unsafe;
}

static class SuperArray {
    private final static int BYTE = 1;
    private long size;
    private long address;

    public SuperArray(long size) {
        this.size = size;
        address = getUnsafe().allocateMemory(size * BYTE);
    }
    public void set(long i, byte value) {
        getUnsafe().putByte(address + i * BYTE, value);
    }
    public int get(long idx) {
        return getUnsafe().getByte(address + idx * BYTE);
    }
    public long size() {
        return size;
    }
}

运行结果:
结果

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-09-12 20:37:43  更:2021-09-12 20:38:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:49:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码