Java并发知识体系持续更新:
https://blog.csdn.net/m0_46144826/category_9881831.html
在一般的SSM框架增删改查中,我们很少需要用到多线程协作。(应该是基本用不到)
但是在稍微偏向技术方面,甚至是,自己做点小玩具,想要更加高效时,就需要用到多线程之间的协调通信。
刚开始学习并发,也不知道全不全。。。
首先要介绍的就是,并发中比较特殊的方法。
wait
和 notify
系列方法是写在 Object
中的, Object
在 JAVA 中的地位,那简直是老祖宗了,除了 Class
外,根本没有其他类有这种地位。。。
所以首先我提出的疑问就是,为什么要把这几个方法写在 Object 中,而不是 Thread 中?
在网上查了半天资料,才发现这居然还是一道号称艰难的面试题。
结论可能会涉及后面的知识,还有一些我都看不懂的,先全部列上:
Object.wait()
,notify 也一样。说实话,大佬的理解我确实看不懂。。。。
同步 和 等待通知 是两个不同的领域,不要把它们看成是相同的或相关的。
同步是提供互斥并确保 Java 类的线程安全,而 wait 和 notify 是两个线程之间的通信机制 。
好嘞,然后继续。wait() 的三个重载方法中,两个方法调用另一个 native 本地方法。
//无参方法,默认 wait(0),无限期等待
public final void wait() throws InterruptedException {
wait(0);
}
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException("nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
timeout++;
}
wait(timeout);
}
最终产生作用的是下面这个本地方法:
/**
* 导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过。
* 当前线程必须持有本身对象监视器。
*
此方法使当前线程(称为T)将自身置入等待 set 集合中,然后放弃该对象的所有同步声明。
*之后线程 T 无法成为线程调度的目标,并且休眠,直到发生四件事情之一:
*
* - 一些其他线程调用该对象的notify方法,并且线程T恰好被任意选择为被唤醒的线程。
*
- 某些其他线程调用此对象的notifyAll方法。
*
- 一些其他线程interrupts线程T。
*
- 指定的实时数量已经过去,或多或少。 然而,如果timeout为零,则不考虑实时,线程等待直到通知。
*
* 然后从该对象的等待set集合中删除线程T ,并重新启用线程调度。
* 然后它以通常的方式与其他线程竞争在对象上进行同步的权限;
* 一旦获得了对象的控制,其对对象的所有同步声明就恢复到现状,也就是在调用wait方法之后的情况。
* 线程T然后从调用wait方法返回。 因此,从返回wait方法,对象和线程的同步状态T正是因为它是当wait被调用的方法。
*
* 线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。
* 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。
* 换句话说,等待应该总是出现在循环中,就像这样:
*
* synchronized (obj) {
* while ()
* obj.wait(timeout);
* ... // Perform action appropriate to condition
* }
*
* 如果当前线程interrupted任何线程之前或在等待时,那么InterruptedException被抛出。
* 如上所述,在该对象的锁定状态已恢复之前,不会抛出此异常。
*
请注意, wait方法,因为它将当前线程放入该对象的等待集,仅解锁此对象;
* 当前线程可以同步的任何其他对象在线程等待时保持锁定。
*
该方法只能由作为该对象的监视器的所有者的线程调用。
* 有关线程可以成为监视器所有者的方法的说明,请参阅notify方法。
*
* @param timeout 等待的最长时间(以毫秒为单位)。
* @throws IllegalArgumentException 如果timeout值为负。
* @throws IllegalMonitorStateException 如果当前线程不是此对象的监视器的所有者
* @throws InterruptedException 如果任何线程在当前线程等待通知之前或当前线程中断当前线程。 当抛出此异常时,当前线程的中断状态将被清除。
*/
public final native void wait(long timeout) throws InterruptedException;
这是官方注解的谷歌翻译。
上面的官方文档的每句话都蛮重要的。。。当然最重要的就两点:
notify 方法则有以下两个:
/**
* 唤醒正在等待对象监视器的单个线程。
* 如果任何线程正在等待这个对象,其中一个被选择被唤醒。
* 选择是任意的,并且由实施器判断发生。
* 线程通过调用wait方法之一等待对象的监视器。
*
* 唤醒的线程将无法继续,直到当前线程放弃此对象上的锁定为止。
* 唤醒的线程将以通常的方式与任何其他线程竞争,这些线程可能正在积极地竞争在该对象上进行同步;
* 例如,唤醒的线程在下一个锁定该对象的线程中没有可靠的权限或缺点。
*
*
该方法只能由作为该对象的监视器的所有者的线程调用。 线程以三种方式之一成为对象监视器的所有者:
*
* - 通过执行该对象的同步实例方法。
*
- 通过执行在对象上synchronized synchronized语句的正文。
*
- 对于类型为Class,的对象,通过执行该类的同步静态方法。
*
* 一次只能有一个线程可以拥有一个对象的显示器。
*
* @throws IllegalMonitorStateException 如果当前线程不是此对象的监视器的所有者
*/
public final native void notify();
/**
* 唤醒正在等待对象监视器的所有线程。 线程通过调用wait方法之一等待对象的监视器。
* 唤醒的线程将无法继续,直到当前线程释放该对象上的锁。
* 唤醒的线程将以通常的方式与任何其他线程竞争,这些线程可能正在积极地竞争在该对象上进行同步;
* 例如,唤醒的线程在下一个锁定该对象的线程中不会有可靠的特权或缺点。
*
该方法只能由作为该对象的监视器的所有者的线程调用。
* 有关线程可以成为监视器所有者的方法的说明,请参阅notify方法。
* @throws IllegalMonitorStateException 如果当前线程不是此对象的监视器的所有者
*/
public final native void notifyAll();
整个栗子测试下:
public class TestMain {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
WaitNotify example = new WaitNotify();
executorService.execute(example::waitObj);
executorService.execute(example::notifyObj);
executorService.shutdown();
System.out.println("main 主线程结束");
}
public static class WaitNotify {
public synchronized void notifyObj() {
System.out.println("notity 调用");
notifyAll();
}
public synchronized void waitObj() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait 结束");
}
}
}
若启动多个线程调用 wait() 方法,则如下图:
PS: 没有设置守护线程,因此 main 方法将永远不会终结。
这里顺便补充下,sleep()
和 wait()
的区别:
sleep() | wait() |
---|---|
Thread 的静态方法 | Object 的方法 |
保留锁 | 释放锁 |
join 方法的作用是使所属的线程对象x正常执行 run() 方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。
join 方法具有使线程排队运行的作用,有些类似同步的运行效果。
join 与 synchronized 的区别是:join 在内部使用 wait()方法进行等待,而 synchronized 关键字使用的是 JVM 底层,使用“对象监视器”原理作为同步。
先来看下 join
方法的源码,其他两个无参和带参方法,最终调用的都是这个 synchronized
方法:
/**
* @param millis 等待时间(以毫秒为单位)
* @throws IllegalArgumentException 如果{@code millis}的值为负
* @throws InterruptedException 如果有任何线程中断了当前线程,抛出此异常时,线程的中断状态将被清除。
*/
public final synchronized void join(long millis) throws InterruptedException {
//记录进入方法的时间
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
//如果线程未死亡,则循环调用 wait
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
//第一次进入,now 为0,等待 millis 毫秒
//第二次进入,now 为已经等待时间,delay小于等于0时跳出
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
从源码中可以发现, join
最后还是基于 wait
方法实现的。
先看个例子,看看如何工作,再画图:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("join 线程的 run 方法");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
for (int i = 0; i < 10; i++) {
if (i == 5){
thread.join(1999);
}else {
System.out.println(System.currentTimeMillis() + ", main 线程循环中:" + i);
}
}
}
运行结果,省略前后:
...
1596276428638, main 线程循环中:4
1596276430640, main 线程循环中:6
join 线程的 run 方法
1596276430640, main 线程循环中:7
...
再放张图,大概示意下工作流程:
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。
这种方式可以在一个 Lock 对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地对指定线程进行通知,所以更加灵活。
这种方式先贴一个大佬的例子,内容有点多,以后再说,我感觉以后肯定会讲到,应该会有专门一篇。
class AwaitTest {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void before() {
lock.lock();
try {
System.out.println("before");
condition.signalAll();
} finally {
lock.unlock();
}
}
public void after() {
lock.lock();
try {
condition.await();
System.out.println("after");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
AwaitTest example = new AwaitTest();
executorService.execute(example::after);
executorService.execute(example::before);
executorService.shutdown();
}
}
https://www.pdai.tech/md/java/thread/java-thread-x-thread-basic.html
https://segmentfault.com/a/1190000019962661
https://www.jianshu.com/p/beb5413c5ce6
https://www.cnblogs.com/Donnnnnn/p/7234934.html