Object的notify,wait,notifyAll

       首先需要说明的是声明在Object类中声明的方法是java每个类都应该具备的特性,因为众所周知,Object是java所有类的鼻祖,那么Object中的这三个方法是干嘛用的呢?一句话总结:用来控制java线程的状态,或者说是用来做线程同步的。


        首先了解三个基本概念,

        线程同步:多线程并发完成任务,可能需要线程之间的执行有先后顺序,线程A做任务必须等待线程B完成后才可以做。

        线程互斥:多个线程并发执行,只有一个线程能够同时执行某任务,一般体现在代码段的执行上,比如说A线程在访问一个类方法的时候,B线程不应该能同时访问该方法,因为该方法内部访问了一个线程不安全的容器,如ArrayList,如果同时访问该函数就会造成ArrayList的数据不一致,朴素点说就是管理上的混乱。

        java线程的七种状态:new、dead、runnable、running、timed_waiting、waiting、blocked

        

        了解完以上概念之后我们看下jvm如何实现线程的互斥与同步的:

        java实现这种线程之间的同步和线程互斥都是通过对象锁来完成的,也就是众所周知的synchronized关键字,通过它能够锁定一个对象(可能是实例对象,也可能是一个Class对象【注:静态锁定】),通过这种锁定可以轻松的实现多线程的互斥,步骤大致如此:

                1、线程进入一个注明synchronized关键字的代码段之前必须获取声明对象的锁,java的每个对象内部都有一个内置锁,jvm负责修改该锁被哪个线程占用的信息

                2、若该对象的锁未被占用,则当前线程获取该对象锁,jvm修改信息,该线程可以继续执行synchronized中的代码段,此时线程处于runnable或running状态(区别在于是否获得了CPU时间)

                3、若该对象锁已经被其他线程占用,则java虚拟机暂停当前请求线程的运行,使其处于blocked状态,当占用锁的线程运行完成后java虚拟机的线程调度器会负责将正在等待该锁的线程由blocked池置入到runnable池中,并将对象锁赋予给它,被唤起的线程此时处于runnabel状态,等待获取CPU时间后就可以继续运行了(running)。

        

       现在问题来了?通过上面的分析我们发现通过synchronized只能实现了线程间的互斥,那么多线的的同步问题怎么解决呢,通过上面的分析我们发现,一个线程的停与起都不受自己的主动控制,而是通过锁机制被java虚拟机被动的调度。这明显实现不了线程同步的机制,所以jvm引入了wait、notify的机制来解决此问题。

       

       首先需要声明的是wait和notify的设计依据是对象锁,所以在执行wait和notify方法的时候,必须首先获取对象锁。以下是线程同步的基本步骤:

       (1)假设线程A正在运行处于running状态,它想停止运行,等待线程B执行完一段同步代码后它再回来接着运行,显然这种停止要带有某种标记,以通知线程B执行完某段代码后可以把自己唤起,那么首先线程A要获取一个对象锁L(任意一个对象的内置锁都可以)。

       (2)获取L后,线程A在想停止运行的地方执行wait,wait的设计正是为了解决这个问题,其实wait的核心只是做了两件事:1、释放当前线程已经获取的对象锁L;2、jvm将本线程由running置于wating或者timed_waiting状态,其实对象锁L就是(1)中所说的标记,wait释放对象锁其实告诉了jvm它处于waiting状态后在等待什么(其实还是该对象锁L)

       (3)A通过wait释放了对象锁后,因为B因为在同步代码块之前也要获取该对象锁,所以在A释放对象锁之前它处于blocked状态,A释放了对象锁之后它由blocked状态改为了runnable状态,获取CPU时间后改为了running状态,并获取了对象锁L,进入了同步代码块。

       (4)而notify的作用在于通知jvm将等待同一个对象锁的线程至于blocked状态,仅此而已,再无其它,千万不要以为notify本身会释放该对象锁的功能,no它做的只是修改waiting线程的状态,线程B在执行完notify后线程A的状态改为了blocked。

       (5)线程B继续运行,直到所有的同步代码块执行完成,此时jvm释放了B对对象锁L的占用,jvm会从blocked状态的所有线程中选择等待该对象锁的线程A并修改其状态为runnable(概率性选择)。

       (6)线程A获取CPU时间,继续wait之后的代码运行

       

      下面举例说明以上过程:

      首先我随意写了一个Mutex对象,用于互斥对象

      

package com.wanyonghui.test.notify;

public class Mutex {
	
}

      然后定义了两个线程,一个对应上面的线程A,一个对应上面的线程B

package com.wanyonghui.test.notify;

/*
 * 该线程先打出一句话“我是线程WaitThread begin”,然后等待NotifyTread线程完成任务后才打印另一句话“我是线程WaitThread end”
 */
public class WaitThread extends Thread{
	
	private Mutex mutex;
	
	public WaitThread(Mutex mutex){
		
		this.mutex = mutex;
	}
	
	public void run(){
		
			try {
				
				System.out.println("我是线程WaitThread begin");
				synchronized(mutex){
					
					mutex.wait();
				}
				System.out.println("我是线程WatiThread end");
				
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
	}
	
}


package com.wanyonghui.test.notify;

/*
 * 该线程先睡觉3秒钟,然后打出一句话“我是线程NotifyThread processing”,最后唤醒另一个线程”
 */
public class NotifyThread extends Thread{
	
	private Mutex mutex;
	
	public NotifyThread(Mutex mutex){
		
		this.mutex = mutex;
	}
	
	public void run(){
		
		try {
			Thread.sleep(3000);
			
			synchronized(mutex){
				System.out.println("我是线程NotifyThread processing");
				mutex.notify();
				Thread.sleep(3000);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		
	}
		
}

下面是测试类

package com.wanyonghui.test.notify;

public class TestNotify {
	
	public static void main(String[] args) {
		
		Mutex mutex = new Mutex();
		
		NotifyThread wt = new NotifyThread(mutex);
		WaitThread nt = new WaitThread(mutex);
		
		wt.start();
		nt.start();
		
	}
}

运行结果:

我是线程WaitThread begin(3秒后显示下一行)

我是线程NotifyThread processing(3秒后显示下一行)

我是线程WatiThread end







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值