java并发编程-线程通讯机制(为什么notify(), wait()等函数定义在Object中,而不是Thread中)

硅谷探秘者 1619 0 0

1.先看一下线程的生命周期转换图(学java的此图必背)

        本篇文章的主要目的不是分析线程的各种状态之间的转换,而主要是研究一下线程之间的通讯机制,以及Object的wait方法和notify方法。所以下面就以这两个方法为主展开讨论。

首先说明者两个方法的作用:

  1. wait():使调用该方法的线程释放该同步锁资源,然后从运行状态退出,进入等待队列,直到被再次唤醒。
  2. notify():随机唤醒等待队列中等待同一同步锁资源的一个线程,并使该线程退出等待队列,进入锁池状态,在获取同步锁对象后进入可运行状态,也就是notify()方法仅通知一个线程。
  3. notifyAll():使所有正在等待队列中等待同一同步锁资源的全部线程退出等待队列,进入锁池状态开始竞争同步锁资源,竞争到同步锁资源的,进入可运行状态。然后在获取到时间片后开始执行代码。

        从上述对方法的说明中可以看出,这几个方法的执行都和同步锁有关,没错,这也是下面要说明的一点,那就是wait方法和notify/notifyAll方法的执行必须在同步代码块中。因为在介绍wait方法的时候说过,在调用同步资源的wait方法的时候会使调用该方法的线程释放同步锁,既然要释放同步锁,那调用方法之前就要先获取同步锁。notify方法也是一样,另外notify()是依据什么唤醒等待线程的呢?答案就是一直提到的同步锁对象。

举个简单的例子

public class A8 {
	public static void main(String[] args) {
		A8 a=new A8();
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (a) {
					a.test();
				}
			}
		}).start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (a) {
					a.test2();
				}
			}
		}).start();
	}
	public void test() {
		try {
			this.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("---");
	}
	public void test2() {
		this.notify();
		System.out.println("***");
	}
}

当然也可以写成这个样子

public class A8 {
	public static void main(String[] args) {
		A8 a=new A8();
		new Thread(new Runnable() {
			@Override
			public void run() {
				a.test();
			}
		}).start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(new Runnable() {
			@Override
			public void run() {
				a.test2();
			}
		}).start();
	}
	public synchronized void test() {
		try {
			this.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("---");
	}
	public synchronized void test2() {
		this.notify();
		System.out.println("***");
	}
}

要实现线程之间的通讯,多个线程锁住的同步资源必须是同一个对象。如果是下方这样的可不行哟!

public static void main(String[] args) {
		A8 a=new A8();
		A8 a2=new A8();
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (a) {
					a.test();
				}
			}
		}).start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (a2) {
					a2.test2();
				}
			}
		}).start();
	}

如果在没有获取同步资源时调用wait/notify时则会报错,如下例子:

public class A8 {
	public static void main(String[] args) {
		A8 a=new A8();
		new Thread(new Runnable() {
			@Override
			public void run() {
				a.test();
			}
		}).start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(new Runnable() {
			@Override
			public void run() {
				a.test2();
			}
		}).start();
	}
	public void test() {
		try {
			this.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("---");
	}
	public void test2() {
		this.notify();
		System.out.println("***");
	}
}

报错:

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Unknown Source)
	at a.A8.test(A8.java:26)
	at a.A8$1.run(A8.java:9)
	at java.lang.Thread.run(Unknown Source)
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at a.A8.test2(A8.java:33)
	at a.A8$2.run(A8.java:20)
	at java.lang.Thread.run(Unknown Source)

2.再来说说 为什么notify(), wait()等函数定义在Object中,而不是Thread中

        Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。

        wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
        线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。

        负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。

        总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。


评论区
请写下您的评论...
暂无评论...
猜你喜欢
java基础 2961 1.线线之间信的两个基本问题互斥和同步。线同步线之间所具有的一种约关系,一个线的执行依赖另一个线的消息,当它没有得到另一个线的消息时应待,直到消息到达时才被唤醒
java基础 1287 java线之生产者消费者模式生产者消费者模式、多线经典的设计模式,生产者和消费者过分离的执行工作解耦,简化了开模式,生产者和消费者可以以同的速度生产和消费据。一个生产和消
java基础 1468 java-理解CAS算法1.cas算法?CAS:CompareandSwap,即比较再交换。jdk5增加了包java.util.concurrent.*,其下面的类使用CAS算法实
java虚拟机(jvm) 1416 硬件的效率与一致性正式讲解Java虚拟相关的知识之前,我们先花费一点时间去了解一下物理计算问题,物理遇到的问题与虚拟的情况有少相似之处,物理的处理方案对于虚拟
java基础 2829 线池:1.创建/销毁线伴随着系统开销,过于频繁的创建/销毁线,会很大度上影响处理效率2.线量过多,抢占系统资源从导致阻塞3.对线进行一些简单的管理
java基础 3545 java之service层处理事务加锁可能会无效最近注意到一个问题--service层处理要多次操作据库事务时往往要@Transactional事务注解,这个时候就要注意了,如果
java基础 1600 Java两种线:用户线和守护线所谓守护线序运行的时候后台提供一种用服务的线,比如垃圾回收线一个很称职的守护者,且这种线属于可或缺的部分。因此,当所有的
official 810 cpu资源和锁资源)且释放锁标记,jvm会把该线放入待池会自动唤醒该线,必须由其他线调用同一锁对象的notify方法或notifyAll方法或者wait时间到则该线进入锁池状态,
归档
2018-11  12 2018-12  33 2019-01  28 2019-02  28 2019-03  32 2019-04  27 2019-05  33 2019-06  6 2019-07  12 2019-08  12 2019-09  21 2019-10  8 2019-11  15 2019-12  25 2020-01  9 2020-02  5 2020-03  16 2020-04  4 2020-06  1 2020-07  7 2020-08  13 2020-09  9 2020-10  5 2020-12  3 2021-01  1 2021-02  5 2021-03  7 2021-04  4 2021-05  4 2021-06  1 2021-07  7 2021-08  2 2021-09  8 2021-10  9 2021-11  16 2021-12  14 2022-01  7 2022-05  1 2022-08  3 2022-09  2 2022-10  2 2022-12  5 2023-01  3 2023-02  1 2023-03  4 2023-04  2 2023-06  3 2023-07  4 2023-08  1 2023-10  1 2024-02  1 2024-03  1 2024-04  1
标签
算法基础 linux 前端 c++ 数据结构 框架 数据库 计算机基础 储备知识 java基础 ASM 其他 深入理解java虚拟机 nginx git 消息中间件 搜索 maven redis docker dubbo vue 导入导出 软件使用 idea插件 协议 无聊的知识 jenkins springboot mqtt协议 keepalived minio mysql ensp 网络基础 xxl-job rabbitmq haproxy srs 音视频 webrtc javascript
目录
没有一个冬天不可逾越,没有一个春天不会来临。最慢的步伐不是跬步,而是徘徊,最快的脚步不是冲刺,而是坚持。