вопрос
(1) Что небезопасно?
(2) У Unsafe есть только функция CAS?
(3) Почему небезопасно небезопасно?
(4) Как использовать небезопасно?
Введение
Эта глава является первой главой темы параллельного пакета java, но первой является не класс в параллельном пакете java, а волшебный класс sun.misc.Unsafe в java.
Unsafe предоставляет нам механизм доступа к базовому слою, который используется только основной библиотекой классов java и не должен использоваться обычными пользователями.
Однако, чтобы лучше понять экосистему Java, мы должны изучить ее и понять, не углубляться в лежащий в ее основе код C/C++, а понимать ее основные функции.
Получить экземпляр небезопасного
Глядя на исходный код Unsafe, мы обнаружим, что он предоставляет статический метод getUnsafe().
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
Однако прямой вызов этого метода вызовет исключение SecurityException, поскольку Unsafe используется только внутренними классами Java, а внешние классы не должны его использовать.
Значит, у нас нет способа?
Конечно нет, у нас есть отражения! Глядя на исходный код, мы обнаружили, что у него есть атрибут под названием theUnsafe, мы можем получить его напрямую через отражение.
public class UnsafeTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
}
}
Создайте экземпляр класса с помощью Unsafe
Предположим, у нас есть простой класс:
class User {
int age;
public User() {
this.age = 10;
}
}
Если мы создадим экземпляр этого класса через конструктор, свойство age вернет 10.
User user1 = new User();
// 打印10
System.out.println(user1.age);
Что, если мы вызовем Unsafe для его создания?
User user2 = (User) unsafe.allocateInstance(User.class);
// 打印0
System.out.println(user2.age);
возраст вернет 0, потому чтоUnsafe.allocateInstance()
Объекту будет выделена только память, а конструктор вызываться не будет, поэтому здесь будет возвращено только значение по умолчанию 0 типа int.
Изменить значение частного поля
Используя методы Unsafe putXXX(), мы можем изменить значение любого частного поля.
public class UnsafeTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
User user = new User();
Field age = user.getClass().getDeclaredField("age");
unsafe.putInt(user, unsafe.objectFieldOffset(age), 20);
// 打印20
System.out.println(user.getAge());
}
}
class User {
private int age;
public User() {
this.age = 10;
}
public int getAge() {
return age;
}
}
Как только мы получим поле age через вызов отражения, мы можем использовать Unsafe, чтобы изменить его значение на любое другое значение int. (Конечно, здесь его также можно напрямую изменить путем отражения)
генерировать проверенное исключение
Мы знаем, что если код выдает проверенное исключение, либо используйте try...catch, чтобы перехватить его, либо определите исключение в сигнатуре метода, но с Unsafe мы можем выдать проверенное исключение без перехвата или определить его в методе. подпись.
// 使用正常方式抛出IOException需要定义在方法签名上往外抛
public static void readFile() throws IOException {
throw new IOException();
}
// 使用Unsafe抛出异常不需要定义在方法签名上往外抛
public static void readFileUnsafe() {
unsafe.throwException(new IOException());
}
Используйте память вне кучи
Если в процессе работы памяти на JVM будет недостаточно, это приведет к частому сбору мусора. В идеале мы могли бы рассмотреть возможность использования памяти вне кучи, т. е. части памяти, которая не управляется JVM.
Используя unsafe allocateMemory(), мы можем выделить память непосредственно вне кучи, что может быть очень полезно, но мы должны помнить, что эта память не управляется JVM, поэтому нам нужно вызвать freeMemory(), чтобы освободить ее вручную.
Предположим, мы хотим создать огромный массив int вне кучи, мы можем сделать это с помощью метода allocateMemory():
class OffHeapArray {
// 一个int等于4个字节
private static final int INT = 4;
private long size;
private long address;
private static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 构造方法,分配内存
public OffHeapArray(long size) {
this.size = size;
// 参数字节数
address = unsafe.allocateMemory(size * INT);
}
// 获取指定索引处的元素
public int get(long i) {
return unsafe.getInt(address + i * INT);
}
// 设置指定索引处的元素
public void set(long i, int value) {
unsafe.putInt(address + i * INT, value);
}
// 元素个数
public long size() {
return size;
}
// 释放堆外内存
public void freeMemory() {
unsafe.freeMemory(address);
}
}
Вызовите allocateMemory() в конструкторе, чтобы выделить память, и вызовите freeMemory(), чтобы освободить память после использования.
Он используется следующим образом:
OffHeapArray offHeapArray = new OffHeapArray(4);
offHeapArray.set(0, 1);
offHeapArray.set(1, 2);
offHeapArray.set(2, 3);
offHeapArray.set(3, 4);
offHeapArray.set(2, 5); // 在索引2的位置重复放入元素
int sum = 0;
for (int i = 0; i < offHeapArray.size(); i++) {
sum += offHeapArray.get(i);
}
// 打印12
System.out.println(sum);
offHeapArray.freeMemory();
Наконец, не забудьте вызвать freeMemory(), чтобы освободить память обратно для операционной системы.
Операция сравнения и замены
JUC использует множество операций CAS, описанных ниже, и их нижний уровень — это вызываемый метод Unsafe CompareAndSwapXXX(). Этот подход широко используется в алгоритмах без блокировок, и он может использовать инструкции процессора CAS для обеспечения значительного ускорения по сравнению со стандартным пессимистическим механизмом блокировки в Java.
Например, мы можем создавать потокобезопасные счетчики на основе метода compareAndSwapInt() из Unsafe.
class Counter {
private volatile int count = 0;
private static long offset;
private static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public void increment() {
int before = count;
// 失败了就重试直到成功为止
while (!unsafe.compareAndSwapInt(this, offset, before, before + 1)) {
before = count;
}
}
public int getCount() {
return count;
}
}
Мы определяем переменное поле count, чтобы его изменения были видны всем потокам, и получаем адрес смещения count в классе при загрузке класса.
В методе increment () мы пытаемся обновить значение count, полученное ранее, вызвав метод compareAndSwapInt () Unsafe, Если он не был обновлен другими потоками, обновление выполнено успешно, в противном случае оно продолжает повторяться, пока не добьется успеха.
Мы можем протестировать наш код, используя несколько потоков:
Counter counter = new Counter();
ExecutorService threadPool = Executors.newFixedThreadPool(100);
// 起100个线程,每个线程自增10000次
IntStream.range(0, 100)
.forEach(i->threadPool.submit(()->IntStream.range(0, 10000)
.forEach(j->counter.increment())));
threadPool.shutdown();
Thread.sleep(2000);
// 打印1000000
System.out.println(counter.getCount());
park/unpark
JVM использует два очень мощных метода park() и unpark() в Unsafe при переключении контекста.
Когда поток ожидает операции, JVM вызывает метод unsafe park(), чтобы заблокировать поток.
Когда заблокированный поток необходимо запустить снова, JVM вызывает метод Unsafe unpark(), чтобы разбудить поток.
Мы видели много LockSupport.park()/unpark() при анализе коллекций в java раньше, и все они вызывают эти два метода Unsafe внизу.
Суммировать
С Unsafe возможно почти все:
(1) Создать экземпляр класса;
(2) изменить значение частного поля;
(3) генерировать проверенное исключение;
(4) Используйте память вне кучи;
(5) работа CAS;
(6) Блокировать/разбудить поток;
пасхальные яйца
На пути к созданию экземпляра класса?
(1) Создать экземпляр класса через конструктор;
(2) создать экземпляр класса через класс;
(3) Создать экземпляр класса посредством отражения;
(4) Создать экземпляр класса путем клонирования;
(5) Создать экземпляр класса путем десериализации;
(6) Создайте экземпляр класса через Unsafe;
public class InstantialTest {
private static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
// 1. 构造方法
User user1 = new User();
// 2. Class,里面实际也是反射
User user2 = User.class.newInstance();
// 3. 反射
User user3 = User.class.getConstructor().newInstance();
// 4. 克隆
User user4 = (User) user1.clone();
// 5. 反序列化
User user5 = unserialize(user1);
// 6. Unsafe
User user6 = (User) unsafe.allocateInstance(User.class);
System.out.println(user1.age);
System.out.println(user2.age);
System.out.println(user3.age);
System.out.println(user4.age);
System.out.println(user5.age);
System.out.println(user6.age);
}
private static User unserialize(User user1) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://object.txt"));
oos.writeObject(user1);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D://object.txt"));
// 反序列化
User user5 = (User) ois.readObject();
ois.close();
return user5;
}
static class User implements Cloneable, Serializable {
private int age;
public User() {
this.age = 10;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
}
Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись «Брат Тонг читает исходный код», проверить больше статей из серии исходного кода и поплавать в океане исходного кода с братом Тонгом.
