关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。
我们继续总结学习Java基础知识,温故知新。
LockSupport 是 Java SE 9 及以上版本中引入的一个线程同步工具类,用于支持同步方法,提供了多种同步机制.
LockSupport 所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。
LockSupport 底层调用的是Unsafe中的native代码。
java.util.concurrent并发包下很多并发类的底层加锁都是基于LockSupport,如ReentrantLock、CountDownLatch、ParkableLazyDeque 等。
LockSupport 类中最常用的方法是 Park() 和 Unpark() 方法,分别用于阻塞和唤醒线程。
Thread a = new Thread(() -> {
System.out.println("start " + Thread.currentThread().getName());
int resule = 0;
for(int i = 0; i < 10; i ++) {
resule += i;
}
// 阻塞线程
LockSupport.park();
System.out.println(resule);
}, "a");
a.start();
try {
// TimeUnit.MILLISECONDS.sleep(1000);
} catch (Exception e) {
}
// 释放,这里不调用的话,上面将一直锁定
LockSupport.unpark(a);
park和unpark都是直接调用了Unsafe的方法,
我们这么理解:这两个方法的原理是基于一个计数器,计数器的值决定了线程的阻塞等待状态。当计数器的值减为 0 时,表示线程可以被唤醒,执行相应的操作。
此外,LockSupport 还提供了一些同步方法,如 Lock 和 Unlock() 方法,用于获取和释放锁。Lock 方法用于获取锁,Unlock() 方法用于释放锁。与 synchronized 关键字不同,Lock 和 Unlock() 方法不需要同时进行,可以在任意一个方法中获取锁,并且可以在任意一个方法中释放锁,因此更加灵活和方便。
调用park后,此时线程处于waiting的状态
public static void park() {
UNSAFE.park(false, 0L);
}
public static void park(Object blocker) {
// 获取当前线程
Thread t = Thread.currentThread();
// 设置Blocker
setBlocker(t, blocker);
// 此后,当前线程就已经阻塞了,后面的函数无法运行,直至unpark函数被调用
UNSAFE.park(false, 0L);
//unpark函数被调用后,继续执行,如果没有第二个setBlocker,那么之后没有调用park(Object blocker),
//而直接调用getBlocker函数,得到的还是前一个park(Object blocker)设置的blocker,显然是不符合逻辑的
setBlocker(t, null);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
Unsafe.java
public native void unpark(Object var1);
public native void park(boolean var1, long var2);
// Hotspot implementation via intrinsics API
private static final sun.misc.Unsafe UNSAFE;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
} catch (Exception ex) { throw new Error(ex); }
}
Thread.sleep()和LockSupport.park() 都是阻塞当前线程的执行,
都 不会 释放 当前线程占有的锁资源;
Thread.sleep()方法需要捕获中断异常。
LockSupport.park()不需要捕获中断异常。
Object类中的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在sychronized内部执行
在 Object.wait() 方法中,首先使用 synchronized 关键字锁定当前对象,然后调用 Object.wait() 方法等待另一个线程执行完毕并返回一个 Future 对象。在等待过程中,当前线程会一直阻塞,直到另一个线程执行完毕并返回一个 Future 对象。当等待线程执行完毕后,Future 对象的 get() 方法将返回等待线程的结果。因此,在使用 Object.wait() 方法时,需要确保等待线程已经执行完毕,否则可能会导致死锁等问题。
Java中的Condition是java.util.concurrent.locks包下的一个接口,它主要用于在多线程环境中控制线程的等待和唤醒。
线程需要获得并持有锁,必须在锁块(synchronized或lock中)
必须要先等待后唤醒,线程才能够被唤醒。
Thread.sleep()不会释放锁资源,到时间了会自动唤醒,然后继续执行;
Object.wait()会释放锁资源。需要另一个线程使用Object.notify()唤醒
二者都会阻塞当前线程的运行。
Object.wait()方法需要在synchronized块中执行;
LockSupport.park()可以在任意地方执行;
Object.wait()方法需要捕获中断异常。
LockSupport.park()不需要捕获中断异常。
Object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;
LockSupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;
Object.wait()和Condition.await()的原理是基本一致的,不同的是Condition.await()底层是调用LockSupport.park()来实现阻塞当前线程的。
同步代码块通常由 synchronized 关键字修饰
在Java中,notify() 和 wait() 方法通常用于实现线程之间的通信和同步。这些方法通常在同步代码块中执行,以确保线程安全和原子性。
在同步代码块中执行 notify() 和 wait() 方法可以确保线程安全和原子性,因为它们可以保证在同一时刻只有一个线程可以执行这些方法。如果在非同步代码块中执行这些方法,则可能会导致竞争条件和死锁等问题。
如果当前的线程不是此对象锁的所有者,却调用该对象的notify()或wait()方法时抛出IllegalMonitorStateException异常;
如果当前线程是此对象锁的所有者,wait()将一直阻塞,因为后续将没有其它notify()唤醒它。
线程不会被阻塞,直接跳过park(),继续执行后续内容,大家可以自己尝试下
Thread a = new Thread(() -> {
System.out.println("start " + Thread.currentThread().getName());
int resule = 0;
for(int i = 0; i < 10; i ++) {
resule += i;
}
LockSupport.park();
System.out.println(resule);
}, "a");
try {
// TimeUnit.MILLISECONDS.sleep(1000);
} catch (Exception e) {
}
LockSupport.unpark(a);
a.start();
LockSupport.unpark(a);
Java 专栏
SQL 专栏
数据结构与算法
Android学习专栏