Object 的wait()方法和notify()方法
public static void main(String[] args) {
Object objectLock = new Object();
new Thread(() -> {
synchronized (objectLock) {
try {
System.out.println(Thread.currentThread().getName() + ",come in");
objectLock.wait();
System.out.println(Thread.currentThread().getName() + ",被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
//暂停几秒,保证t1线程先进入synchronized代码块
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify();
System.out.println(Thread.currentThread().getName() + ",发出通知");
}
}, "t2").start();
}
打印结果:
t1,come in
t2,发出通知
t1,被唤醒
现在案例是我们先让t1获得objectLock,t1紧接着调用objectLock的wait()方法交出锁的控制权,等t2运行拿到objectLock的锁对象,在调用objectLock的notify()方法唤醒t1,也就是说t1和t2的等待和唤醒是有先后顺序的。那如果我们先调用t2的唤醒,在调用t1的等待,会有什么结果?结果就是t1永远不会被唤醒。
Condition的await()方法和signal()方法
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + ",come in");
condition.await();
System.out.println(Thread.currentThread().getName() + ",被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t1").start();
//暂停几秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + ",发出通知");
} finally {
lock.unlock();
}
}, "t2").start();
}
打印结果:
t1,come in
t2,发出通知
t1,被唤醒
案例中,t1先获得lock对象,然后在调用lock对象中的Condition的await()方法让出lock对象的控制权。t2拿到lock的控制控制权之后,然后调用lock对象的Condition的signal()方法唤醒t1,完成了对t1线程的等待和唤醒流程。如果这里我们和Object 的wait()方法和notify()案例中一样先让t2调用condition.signal()唤醒方法,在让t1调用condition.await()方法进行等待,t1是否能正常的被唤醒呢?答案是t1将不能被唤醒,t1将永远等待下去。
LockSupport的park()方法和unpark()方法
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ",come in");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + ",被唤醒");
}, "t1");
t1.start();
//等待几秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + ",发送通知");
}, "t2").start();
}
打印结果:
t1,come in
t2,发送通知
t1,被唤醒
LockSupport
是Java中的一个工具类,用于实现线程的阻塞和唤醒操作。它提供了以下几个方法:
-
park()
方法:- 当一个线程调用
LockSupport.park()
时,它会被阻塞,进入等待状态。 park()
方法不需要依赖任何对象的锁,因此可以在任意地方使用。
- 当一个线程调用
-
unpark(Thread thread)
方法:- 当一个线程调用
LockSupport.unpark(thread)
时,它会将指定的线程解除阻塞,使其从等待状态转变为可运行状态。 - 如果指定的线程之前没有被阻塞,则下一次调用该线程的
park()
方法时,它仍然会被阻塞。
- 当一个线程调用
LockSupport
与传统的等待/唤醒机制相比有以下几个特点:
LockSupport
是基于线程而不是对象的,它不需要依赖于某个对象的锁来进行阻塞和唤醒操作。- 调用
park()
方法后,线程不需要被其他线程唤醒就可以自动解除阻塞。 unpark(thread)
方法可以先于park()
方法调用,这样即使线程还没有被阻塞也能够保证下一次调用park()
时不会被阻塞。
形象的理解:
线程阻塞需要消耗凭证(permit),这个凭证最多只有一个。当调用park方法时,如果有凭证则会直接消耗掉这个凭证然后正常退出,如果没有凭证,就必须阻塞等待凭证可用。unpark则相反,它会增加一个凭证,但最多只能有一个凭证,累加无效。