用死循环的方式,一直尝试获取锁。在任务耗时比较长或者并发冲突比较大的时候,就不适合。
因为任务耗时比较长或者并发冲突比较大的时候,可能要循环上万次都不止,才能获取到锁,太消耗 CPU 。
这种场景下,理想的解决方案:
- 线程执行任务的条件不满足时,阻塞自己,进入等待状态;当线程执行的任务条件满足时,通知等待的线程继续执行。
- 线程阻塞的方式,能够避免循环等待对 CPU 的消耗。
在 Java 中实现线程等待和唤醒,有多种方式,比较常见的有以下两种:
1、synchronized 加 wait()、notify()、notifyAll()
- 调用 wait() 方法后,当前线程就会被阻塞,进入等待队列中,释放持有的互斥锁
- 条件满足时调用 notify(),会通知等待队列中的某个线程,告诉它条件满足可以继续执行
- 条件满足时调用 notifyAll(),会通知等待队列中的所有线程,告诉它们条件满足可以继续执行
- 只能在 synchronized 加锁的对象上调用 wait() 、notify()、notifyAll() 方法,否则报 java.lang.IllegalMonitorStateException
看下这个例子
package constxiong.concurrency.a025;
import java.util.HashSet;
import java.util.Set;
/**
* 测试 线程等待/唤醒
* @author ConstXiong
* @date 2019-09-27 15:44:48
*/
public class TestWaitAndNotify {
//单例的资源管理类
private final static MangerWaitAndNotify manager = new MangerWaitAndNotify();
//资源1
private static Object res1 = new Object();
//资源2
private static Object res2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
manager.applyResources(res1, res2);
//向管理类,申请res1和res2,申请失败,重试
try {
System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
synchronized (res1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
//休眠 3 秒
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (res2) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
}
}
} finally {
manager.returnResources(res1, res2);//归还资源
}
}).start();
new Thread(() -> {
manager.applyResources(res1, res2);
try {
System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
synchronized (res2) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
//休眠 1秒
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (res1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
}
}
} finally {
manager.returnResources(res1, res2);//归还资源
}
}).start();
}
}
/**
* 资源申请、归还管理类
* @author ConstXiong
* @date 2019-09-24 14:10:57
*/
class MangerWaitAndNotify {
//资源存放集合
private Set<Object> resources = new HashSet<Object>();
/**
* 申请资源
* @param res1
* @param res2
* @return
*/
synchronized void applyResources(Object res1, Object res2) {
while (resources.contains(res1) || resources.contains(res1)) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
resources.add(res1);
resources.add(res2);
}
/**
* 归还资源
* @param res1
* @param res2
*/
synchronized void returnResources(Object res1, Object res2) {
resources.remove(res1);
resources.remove(res2);
notifyAll();
}
}
打印结果
线程:Thread-0 申请 res1、res2 资源成功
线程:Thread-0 获取到 res1 资源的锁
线程:Thread-0 获取到 res2 资源的锁
线程:Thread-1 申请 res1、res2 资源成功
线程:Thread-1 获取到 res2 资源的锁
线程:Thread-1 获取到 res1 资源的锁
2、Condition、await()、signal()、signalAll()
- Condition 对象是通过 Lock 对象的 newCondition() 方法获得
- 调用 await() 方法后,当前线程就会被阻塞,进入等待队列
- 条件满足时调用 signal(),会通知等待队列中的某个线程,告诉它条件满足可以继续执行
- 条件满足时调用 signalAll(),会通知等待队列中的所有线程,告诉它们条件满足可以继续执行
- 只能在调用 Lock 对象 lock()方法后,调用 Condition 对象的 wait() 、signal()、signalAll() 方法,否则报 java.lang.IllegalMonitorStateException
示例代码
package constxiong.concurrency.a025;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试 线程等待/唤醒
* @author ConstXiong
* @date 2019-09-27 15:44:48
*/
public class TestAwaitAndSignal {
//单例的资源管理类
private final static MangerAwaitAndNotify manager = new MangerAwaitAndNotify();
//资源1
private static Object res1 = new Object();
//资源2
private static Object res2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
manager.applyResources(res1, res2);
//向管理类,申请res1和res2,申请失败,重试
try {
System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
synchronized (res1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
//休眠 3 秒
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (res2) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
}
}
} finally {
manager.returnResources(res1, res2);//归还资源
}
}).start();
new Thread(() -> {
manager.applyResources(res1, res2);
try {
System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
synchronized (res2) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
//休眠 1秒
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (res1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
}
}
} finally {
manager.returnResources(res1, res2);//归还资源
}
}).start();
}
}
/**
* 资源申请、归还管理类
* @author ConstXiong
* @date 2019-09-24 14:10:57
*/
class MangerAwaitAndNotify {
//资源存放集合
private Set<Object> resources = new HashSet<Object>();
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
/**
* 申请资源
* @param res1
* @param res2
* @return
*/
void applyResources(Object res1, Object res2) {
while (resources.contains(res1) || resources.contains(res1)) {
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
resources.add(res1);
resources.add(res2);
}
/**
* 归还资源
* @param res1
* @param res2
*/
void returnResources(Object res1, Object res2) {
lock.lock();
try {
resources.remove(res1);
resources.remove(res2);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
打印结果
线程:Thread-0 申请 res1、res2 资源成功
线程:Thread-0 获取到 res1 资源的锁
线程:Thread-0 获取到 res2 资源的锁
线程:Thread-1 申请 res1、res2 资源成功
线程:Thread-1 获取到 res2 资源的锁
线程:Thread-1 获取到 res1 资源的锁
文章汇总于 带着问题重学 Java 并发编程
持续更新中...
转载:https://blog.csdn.net/meism5/article/details/101596553
查看评论