文章目录
- 线程间的协作
- sleep(long millis)
- yield( )
- wait() / notify()
- 等待 / 通知的经典范式
- 等待 / 超时模式
- join()
线程间的协作
sleep(long millis)
Thread.sleep(),让当前线程暂停等待时间,让出CPU的使用权,不释放锁。
yield( )
让出当前的CPU时间片,将线程从运行改为就绪,并且再次参与CPU使用权的竞争。也不会释放锁。
wait() / notify()
等待 / 通知机制:
线程A获得了的对象O的锁,调用了对象O的wait方法,进入等待状态,并且释放掉对象O的锁。线程B在获得对象O的锁之后,调用对象O的notify() / notifyAll()方法,通知所有在对象O上的等待对象苏醒。在线程A再次获取到了对象O的锁之后,线程A苏醒,开始继续运行。
两个线程通过对象O来完成交互。
wait(long millis):等待N毫秒,在时间过了之后,没有通知就直接返回。
public class WaitNotifyThread {
private static final Object lock = new Object();
private static boolean flag = true;
private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
static class WaitThread implements Runnable {
@Override
public void run() {
// wait线程首先要拿到对象lock的锁
synchronized (lock) {
System.out.println("wait线程拿到锁,当前时间:" + sdf.format(new Date()));
while (flag) {
try {
System.out.println("当前标识为true,wait线程等待中,当前时间:" + sdf.format(new Date()));
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("wait线程被唤醒,标识为" + flag + ",当前时间:" + sdf.format(new Date()));
}
}
}
static class NotifyThread implements Runnable {
@Override
public void run() {
// 调用notify前,必须对该对象加锁
synchronized (lock) {
flag = false;
System.out.println("notify线程通知所有等待线程唤醒,当前时间:" + sdf.format(new Date()));
lock.notify();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread waitThread = new Thread(new WaitThread());
waitThread.start();
Thread notifyThread = new Thread(new NotifyThread());
notifyThread.start();
}
}
输出结果:
wait线程拿到锁,当前时间:16:30:07
当前标识为true,wait线程等待中,当前时间:16:30:07
notify线程通知所有等待线程唤醒,当前时间:16:30:07
wait线程被唤醒,标识为false,当前时间:16:30:09
- 使用wait()、notify()和notifyAll()时需要对调用对象先加锁。
- 调用wait()方法后,线程状态由运行(RUNNING)改为等待(WAITING),释放对象的锁,进入对象的等待队列。
- notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回。
- notify()方法将等待队列中的一个等待线程移到同步队列,notifyAll()方法将等待队列里的所有线程都移到同步队列中。被移动的线程状态从等待(WAITING)变为阻塞(BLOCKED).
- 从wait()方法返回的前提是获得了调用对象的锁。
Sleep()和wait的区别:
- sleep是让出CPU执行时间,睡眠一会,当时间到就会苏醒,而wait则是交出当前持有对象的锁,然后进入等待状态。
- sleep不会释放锁,wait会释放锁。
- sleep在任何地方都可使用,wait只能放在同步代码块
sleep和yeild的区别:
- sleep让出CPU时不考虑线程的优先级,而yield会考虑线程的优先级,优先给优先级高的线程执行。
- sleep之后,线程会进入等待状态,在指定时间内一定不会执行,而yield只是让出CPU执行时间,会再次参与到CPU竞争中。
等待 / 通知的经典范式
等待方(消费者):
- 获取对象的锁
- 如果条件不满足,调用wait()方法等待
- 条件满足则执行对应的逻辑
synchronized (对象) {
while (条件不满足) {
对象.wait();
}
执行对应的处理逻辑
}
通知方(生产者):
- 获得对象的锁。
- 改变条件
- 调用所有等待在对象上的线程:notifyAll()
synchroized (对象) {
改变条件
对象.notifyAll();
}
例,一个符合生产者和消费者问题的程序:
- 对某一个对象(枪膛)进行操作,其最大容量是20颗子弹,
- 生产者线程(通知方)是一个压入线程,它不断向枪膛中压入子弹,
- 消费者线程(等待方)是一个射出线程,它不断从枪膛中射出子弹。
public class WaitNotifyThreadDemo {
private static final Stack<Integer> STACK = new Stack<>();
private static int MAX_SIZE = 20;
// 生产者
static class Producer implements Runnable {
@Override
public void run() {
// 不断压入子弹
while (true) {
synchronized (STACK) {
if (STACK.empty() || STACK.size() < MAX_SIZE) {
STACK.push(1);
System.out.println("生产者向枪膛压入1个子弹,当前子弹数为:" + STACK.size());
}
// 通知消费者,可以射击了。
STACK.notify();
}
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Customer implements Runnable {
@Override
public void run() {
// 不断射出子弹
while (true) {
synchronized (STACK) {
while (STACK.isEmpty()) {
try {
// 子弹不满,暂时等待。
System.out.println("当前弹夹不满,消费者等待中。。。");
STACK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
STACK.pop();
System.out.println("消费者从枪膛射出1个子弹,当前子弹数为:" + STACK.size());
}
try {
Thread.sleep(new Random().nextInt(900));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread customerThread = new Thread(new Customer());
customerThread.start();
Thread producerThread = new Thread(new Producer());
producerThread.start();
}
}
输出结果:
当前弹夹不满,消费者等待中。。。
生产者向枪膛压入1个子弹,当前子弹数为:1
消费者从枪膛射出1个子弹,当前子弹数为:0
生产者向枪膛压入1个子弹,当前子弹数为:1
消费者从枪膛射出1个子弹,当前子弹数为:0
当前弹夹不满,消费者等待中。。。
生产者向枪膛压入1个子弹,当前子弹数为:1
消费者从枪膛射出1个子弹,当前子弹数为:0
当前弹夹不满,消费者等待中。。。
生产者向枪膛压入1个子弹,当前子弹数为:1
消费者从枪膛射出1个子弹,当前子弹数为:0
等待 / 超时模式
等待 / 超时模式是等待通知模式的升级,在等待通知模式中,如果条件不满足,就一直在循环。对于等待超时模式,给一个超时的时间,在这段时间内,能返回结果,将结果直接返回,超出这段时间,就返回默认结果。
假如超时时间为T,那么在now + T后,程序返回。定义如下变量:
- 等待持续时间:REMAINING = T
- 超时时间:FUTURE = now + T
// 等待超时模式伪代码
public synchronized Object get(long mills) {
// 超时时间:当前时间 + 等待时间
long future = System.currentTimeMills() + mills;
long remaining = mills;
while (条件不满足 && remaining > 0) {
wait(remaining);
remaining = future - System.currentTimeMills();
}
return result;
}
等待超时模式增加了时间的控制,即使方法执行时间过长,也不会永久阻塞调用者,而是会按照调用者的要求按时返回。
等待超时模式模拟获取数据库连接:
public class WaitNotifyOvertime {
static class DbConnection {
// 线程休眠0.5秒,模拟业务操作
void doSomeThing() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class ConnectionPool {
private final LinkedList<DbConnection> dbConnectionPool = new LinkedList<>();
// 初始化连接池
public ConnectionPool(int initSize) {
for (int i = 0; i < initSize; i++) {
dbConnectionPool.addLast(new DbConnection());
}
}
// 释放连接池资源
public void releaseConnection (DbConnection dbConnection) {
if (dbConnection != null) {
synchronized (dbConnectionPool) {
dbConnectionPool.addLast(dbConnection);
dbConnectionPool.notifyAll();
}
}
}
// 从连接池中抓取资源,超时返回
public DbConnection fenchConnection (long millis) {
synchronized (dbConnectionPool) {
// 超时时间小于0,表示一直等待
if (millis <= 0) {
while (dbConnectionPool.isEmpty()) {
try {
dbConnectionPool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return dbConnectionPool.removeFirst();
} else {
// 超时时间
Long future = System.currentTimeMillis() + millis;
// 超时等待时间
Long waiting = millis;
while (dbConnectionPool.isEmpty() && waiting > 0) {
try {
dbConnectionPool.wait(waiting);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 被唤醒后,重新计算等待时间
waiting = future - System.currentTimeMillis();
}
// 这种是数据库连接池有连接了,下面的情况是等待时间到了。
if ( !dbConnectionPool.isEmpty()) {
return dbConnectionPool.removeFirst();
}
return null;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
// 初始化连接池,里面有10个连接
ConnectionPool connectionPool = new ConnectionPool(10);
AtomicInteger got = new AtomicInteger(0);
AtomicInteger notGot = new AtomicInteger(0);
CountDownLatch countDownLatch = new CountDownLatch(30);
// 30个线程需要连接池里的连接
int taskSize = 30;
for (int i = 0; i < taskSize; i++) {
new Thread(() -> {
int count = 20;
// 每个线程尝试拿20次,一共拿600次
while (count > 0) {
DbConnection dbConnection = connectionPool.fenchConnection(1000);
if (dbConnection != null) {
try {
got.incrementAndGet();
dbConnection.doSomeThing();
} finally {
connectionPool.releaseConnection(dbConnection);
}
} else {
notGot.incrementAndGet();
System.out.println(Thread.currentThread().getName() + "获取连接失败。");
}
count--;
}
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println("成功获得连接次数:" + got.get());
System.out.println("未成功获得连接的线程数:" + notGot.get());
}
}
执行结果:
Thread-15获取连接失败。
Thread-19获取连接失败。
成功获得连接次数:348
未成功获得连接的线程数:252
join()
如果一个线程A执行了thread.join()方法,含义是:当前线程A等待thread线程终止之后,从thread.join()返回。
join(long millis):如果在给定的时间内,线程thread没有执行完,那么就直接返回。
public class JoinThread {
public static void main(String[] args) {
Thread previous = Thread.currentThread();
for (int i = 0; i < 5; i++) {
// 每次调用前一个线程的join()方法,等前一个线程执行完再执行.
Thread thread = new Thread(new Runner(previous),String.valueOf(i));
thread.start();
previous = thread;
}
System.out.println(Thread.currentThread().getName() + " terminate.");
}
static class Runner implements Runnable {
private Thread thread;
public Runner(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " terminate.");
}
}
}
输出结果:
main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.
join()方法也满足等待 / 通知模型,当前线程等待前一个线程执行。前一个线程执行完后,调用线程本身的notifyAll()方法,唤醒所有在该线程等待的线程。
加锁;条件不满足一直等待;条件满足方法返回。
public final synchronized void join(long millis){
if (millis == 0) {
// 条件不满足,一直等待
while (isAlive()) {
wait(0);
}
// 条件满足,方法返回
}
}