并发编程—线程协作
这里会记录学习的内容有:等待通知(wait,notify/notifyAll),join()
等待和通知(wait()/notify()/notifyAll())
wait() / notify() / notifyAll()
之前说过wait() / notify() / notifyAll(),他们都是Object类中的方法,使用前都需要先获取锁。
wait():使线程进入等待(阻塞)状态,可设置时间参数。
notify()/notifyAll():唤醒等待中的线程。
等待通知标准范式
等待方:
- 获取对象的锁;
- 循环里判断条件是否满足,不满足调用wait方法;
- 条件满足执行业务逻辑;
通知方:
- 获取对象的锁;
- 改变条件
- 通知所有等待在对象的线程
notify / notifyAll:在实际使用唤醒时尽量使用notifyAll(),防止通知信号丢失。
举个栗子
使用 wait() / notifyAll 完成一个通知的小栗子
// 起始地
private String SITE = "杭州";
/**
* 改变地点
*/
public synchronized void changeSite() {
// 将地点改为上海
this.SITE = "上海";
// 通知(唤醒所有等待的线程)
notifyAll();
}
/**
* 监听地点
*/
public synchronized void waitSite() {
// 监听地点,如果改变就往下执行业务
while ("杭州".equals(this.SITE)) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Core
System.out.println("site is 上海");
}
public static void main(String[] args) throws InterruptedException {
LogisticsDemo logisticsDemo = new LogisticsDemo();
// 启动3个线程监听地点变化
for (int i = 0; i < 3; i++) {
new Thread(() -> {
logisticsDemo.waitSite();
}).start();
}
// 主线程休眠3秒
Thread.sleep(3000);
// 改变地点
logisticsDemo.changeSite();
}
上面代码执行的情况:启动三个线程等待地点变化,主线程休眠三秒后将地点改为上海,三个线程都受到了通知,打印出 site is 上海。
等待超时模式
什么是超时等待?
调用一个方法时,等待一段时间(一般给定一个时间段),如果该方法能够在给定的时间段内得到结果,那么将结果立刻返回,反之,超时返回默认结果。这就是超时等待。
等待/通知的经典范式,即加锁,条件循环和处理逻辑三个步骤,而这种范式无法做到超时等待。
其实在等待通知的标准方式上加以改动就可以实现超时等待。
伪代码如下:
long future = System.currentTimeMillis() + mills;
long remaining = mills;
synchronized (lock) {
while (!condition && remaining > 0) {
wait(remaining);
remaining = future - System.currentTimeMillis();
}
//处理代码
}
举个栗子
使用等待超时模式完成连接池拿取和放连接的方法;
/**
* 在指定超时时间内拿取连接,超时返回null
*
* @param mills 超时时间
* @return
*/
public Connection takeConn(Long mills) throws InterruptedException {
synchronized (pool) {
if (mills < 0) {
if (pool.isEmpty()) {
// 如果池中是空的,则等待
pool.wait();
}
// 返回第一个连接
return pool.removeFirst();
} else {
Long overtime = System.currentTimeMillis() + mills;
Long remainning = mills;
while (pool.isEmpty() && remainning > 0) {
// 连接池为空并且等待时间大于0
pool.wait(remainning);
// 需要等待的时间
remainning = overtime - System.currentTimeMillis();
}
Connection conn = null;
if (!pool.isEmpty()) {
conn = pool.removeFirst();
}
return conn;
}
}
}
/**
* 释放连接
*
* @param conn
*/
public void releaseConn(Connection conn) {
if (conn != null) {
synchronized (pool) {
// 将连接放回连接池
pool.addLast(conn);
// 通知等待的线程
pool.notifyAll();
}
}
}
join()
简单了解
join的单词意思时参加/加入。放在我们这里呢最合适的意思是,插入。join()方法简单来讲就是让线程去插队,完整的描述是这样的:线程A,执行了线程B的join()方法,线程A必须要等待B执行完成了以后,线程A才能继续自己的工作。这句话很好理解,下面就用代码表现出来。
举个栗子
/**
* 线程插队处理类
*/
static class JoinThread implements Runnable {
/**
* 插队的线程
*/
private Thread thread;
public JoinThread(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
// 调用join()方法 【可以理解为插队的动作】
this.thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " run complete.");
}
}
public static void main(String[] args) throws InterruptedException {
// 插队的线程
Thread preThread = Thread.currentThread();
for (int i = 0; i < 10; i++) {
// 第一个插队的线程为 main
Thread thread = new Thread(new JoinThread(preThread), String.valueOf(i));
thread.start();
System.out.println(preThread.getName() + " --> " + thread.getName());
preThread = thread;
}
// 主线程插到了一个位置,也就是说main线程运行完其他线程才能往下执行【主线程的下一个线程是 线程 0】
System.out.println(Thread.currentThread().getName() + " run complete.");
}
上面代码运行的结果:
main --> 0
0 --> 1
1 --> 2
2 --> 3
3 --> 4
4 --> 5
5 --> 6
6 --> 7
7 --> 8
8 --> 9
0 run complete.
1 run complete.
2 run complete.
3 run complete.
4 run complete.
5 run complete.
6 run complete.
7 run complete.
8 run complete.
9 run complete.
可以看出join() 方法可以将线程并行改为串行的执行方式;
我这里只是写了一些join的概念和他的使用。
想深入了解的话请参照文章:https://www.cnblogs.com/aboutblank/p/3631453.html