首页 > 编程 > Java > 正文

java 多线程之wait、notify详解

2019-11-08 01:17:45
字体:
来源:转载
供稿:网友

wait 和 notify以及notifyAll

(1)、方法介绍

## 1.wait、notify以及notifyAll都是Object对象的方法,他们必须在被 synchronized 同步的方法或代码块中调用,否则会报错。

## 2. 调用wait方法会使该线程进入等待状态,并且会释放被同步对象的锁。

## 3. notify操作可以唤醒一个因执行wait而处于阻塞状态的线程,使其进入就绪状态,被唤醒的线程会去尝试着获取对象锁,然后执行wait之后的代码。如果发出notify操作时,没有线程处于阻塞状态,那么该命令会忽略。注意执行notify并不会马上释放对象锁,会等到执行完该同步方法或同步代码块后才释放,下面会有例子来证明。

notify方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,使其退出等待队列进入可运行状态。

4. notifyAll方法可以唤醒等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个线程优先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。

测试在调用notify方法之后并不会马上释放对象锁,而是在执行完同步方法或同步方法块的时候才会释放。

代码如下:

public class MyList { PRivate static List<String> list = new ArrayList<String>(); public static void add(){ list.add("sth"); } public static int getSize(){ return list.size(); }}

main方法以及两个线程类如下:

public class Test { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); ThreadA ta = new ThreadA(lock); Thread tta = new Thread(ta); tta.start(); Thread.sleep(50); ThreadB tb = new ThreadB(lock); Thread ttb = new Thread(tb); ttb.start(); }}class ThreadA implements Runnable{ private Object mLock; public ThreadA(Object lock){ mLock = lock; } public void run() { synchronized (mLock) { if(MyList.getSize() != 5){ try { System.out.println("before wait"); mLock.wait(); System.out.println("after wait"); } catch (InterruptedException e) { e.printStackTrace(); } } } }}class ThreadB implements Runnable{ private Object mLock; public ThreadB(Object lock){ mLock = lock; } public void run() { synchronized (mLock) { for(int i = 0; i< 10; i++){ MyList.add(); if(MyList.getSize() == 5){ mLock.notify(); System.out.println("已发出notify通知"); } System.out.println("增加"+(i+1)+"条数据"); } } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("同步方法之外的方法"); }}

打印结果如下:

before wait增加1条数据增加2条数据增加3条数据增加4条数据已发出notify通知增加5条数据增加6条数据增加7条数据增加8条数据增加9条数据增加10条数据after wait同步方法之外的方法

可以看出2点:

1.执行wait方法后会立马释放对象锁
2.执行notify不会立马释放对象锁,需等该同步方法或同步块执行完。注意是同步的内容执行完,而不是该线程的run方法执行完,从结果最后2句可以看出来。

最后说下 wait和sleep的区别,这也是面试经常面到的问题。

1.sleep是Thread类的方法而wait是Object类的方法。
2.sleep不会立马释放对象锁,而wait会释放。

写个小栗子来证明结论2: 在MyList类中增加一个方法:

public synchronized void doSth(){ System.out.println("Thrad name : "+Thread.currentThread().getName()+" , begain doSth time : "+System.currentTimeMillis()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thrad name : "+Thread.currentThread().getName()+" , end doSth time : "+System.currentTimeMillis()); }

测试main方法

MyList myList = new MyList(); ThreadC tc = new ThreadC(myList); tc.setName("C"); ThreadD td = new ThreadD(myList); td.setName("D"); tc.start(); td.start();

ThreadC和ThreadD类如下:

class ThreadC extends Thread{ private MyList myList; public ThreadC(MyList mlist){ myList = mlist; } @Override public void run() { myList.doSth(); }}class ThreadD extends Thread{ private MyList myList; public ThreadD(MyList mlist){ myList = mlist; } @Override public void run() { myList.doSth(); }}

结果如下:

Thrad name : C , begain doSth time : 1487647524673Thrad name : C , end doSth time : 1487647527674Thrad name : D , begain doSth time : 1487647527674Thrad name : D , end doSth time : 1487647530674

可以看出线程C在调用sleep方法后并不会释放。

最后作点说明: 每个锁对象都有两个队列,就绪队列以及阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,以等待CPU调度。反之一个线程被wait后就会进入阻塞队列,等待下一次唤醒。 也就是说一个线程被wait后会进入阻塞队列,待调用了 notify或notifyAll之后,该线程就会进入就绪队列。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表