【Java Tip】(三) Object类wait()与nofity()

注:本文作者Nemo, http://blog.csdn.net/nemo__
 
 

一、概述

java.lang.Object
    # notify()
    # notifyAll()
    # wait(long timeout)
    # wait(long timeout, int nanos)
    # wait()

       wait()notify()用于协调多个线程对共享数据的存取,synchronized关键字用于保护共享数据,所以必须在synchronized语句块内使用

       wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。

       notify()方法将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。

       notifyAll()方法则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。

 

二、wait()

       当前线程T等待并释放对象锁(object's monitor),直到别的线程调用此对象的notify()或notifyAll(),或超时timeout毫秒。当前线程T必须持有此对象obj的object's monitor。调用这个方法会使用当前线程T暂停,并进入对象obj等待池(wait set),并释放所有关于对象obj的synchronized声明。直到以下种情况会唤醒:

(1) 其它线程调用ojb.notify(),也选择了此线程T作为唤醒线程;  
(2) 其它线程调用ojb.notifyAll();  
(3) 其它线程中断了线程T(Thread#interrupt());  
(4) 超时timeout。  

       当线程T被wakeup后,会从对象obj的等待池中移除,和其它线程竞争obj的持有权(synchronize)。一旦线程T获取obj的持有权,会恢复线程中所有关于obj的synchronize声明,和调用wait()方法之前一样。

       由于某些极少发生的非正常wakeup也会唤醒线程T,因此要求wait()的调用要在while循环中不断检查条件是否满足,当条件不满足时会继续走wait()方法。

       wait()方法会使得当前线程T进入obj的等待池并交出obj的synchronization,但线程中其它对象的synchronization仍会保持住在线程等待期间。

       线程T在wait()方法期间被其它线程调用interrupt()方法,会抛出InterruptedException,这个Exception会直到obj的锁定状态都恢复后才抛出。

 
1. public final native void wait(long timeout) throws InterruptedException;

wait(long timeout)使用方式:

synchronized (obj) {
    while (<condition does not hold>) {
        obj.wait(timeout);
    }
    ... //Perform action appropriate to condition
}

 
2. public final void wait(long timeout, int nanos) throws InterruptedException

       同wait(long timeout)方法,nanos取值在0-999999之间,若nanos > 0,timeout会加1,再执行wait(long timeout)。

 
3. public final void wait() throws InterruptedException

       源码为wait(0),不会有超时设置。

 

三、notify()

       唤醒一个对象锁(object's monitor)的线程,如果有很多个线程在等待,会选择其中一个wakeup,这个选择是任意的但是经过考量的。一个线程等待对象obj的synchronization通过调用obj.wait()方法。

       即使已被notify()唤醒,在当前线程放度对象锁前,被唤醒线程是不能执行的。被唤醒的这个线程要和其它将要synchronize这个obj的线程竞争以获得obj的对象锁。notify()只能在获得到对象obj的对象锁的线程中被调用。

       一个线程成为对象obj的对象锁持有者有三个途径:

(1) 执行对象obj的synchronized方法;  
(2) 执行到以对象为synchronized锁的synchronized块;  
(3) 当obj为Class实例时,执行其静态synchronized方法;  

       在任一时刻,只有一个线程可以持有对象obj的对象锁(object's monitor)。
 
1. public final native void notify();

synchronized (obj) {
    ....
    obj.notify();

    .... //do other things.
}

wakeup某一个线程。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify()或notifyAll()。

 
2. public final native void notifyAll();

       wakeup所有等待obj对象锁的线程。被唤醒的所有线程要和其它将要synchronize这个obj的线程竞争以获得obj的对象锁。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。被唤醒的线程更希望其它线程没有高优先级或没有优势在成为下一个锁定该对象上。

 

四、实例(TaskPersister类)

       TaskPersister是Android ActivityManagerService中一个提供Task留存的一个类,它会把近期的Task和截图保存,重启后恢复到近期任务栏。它的核心是一个LazyTaskWriterThread,及同步数据mWriteQueue。在这个类可以看到synchronized,wait(),notifyAll()的使用。

  1. wakeup()方法。外部不断通过wakeup()向mWriteQueue添加元素,使用synchronized加锁当前实例,在添加完成后调用notifyAll()来通知线程处理是否写入。
void wakeup(TaskRecord task, boolean flush) {
    synchronized (this) {
        if (task != null) {
            ....
            if (queueNdx < 0 && task.isPersistable) {
                mWriteQueue.add(new TaskWriteQueueItem(task));
            }
        } else {
            // Dummy. Ensures removeObsoleteFiles is called when LazyTaskThreadWriter is
            // notified.
            mWriteQueue.add(new WriteQueueItem());
        }

        if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
            mNextWriteTime = FLUSH_QUEUE;
        } else if (mNextWriteTime == 0) {
            mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
        }

        notifyAll();
    }

    yieldIfQueueTooDeep();
}

 
2. saveImage()方法。外部不断通过wakeup()向mWriteQueue添加Image元素,对象锁定对象为当前实例,并会在退出synchronized块时调用notifyAll()通知线程处理。

void saveImage(Bitmap image, String filePath) {
    synchronized (this) {
        ....
        if (queueNdx < 0) {
            mWriteQueue.add(new ImageWriteQueueItem(filePath, image));
        }
        if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
            mNextWriteTime = FLUSH_QUEUE;
        } else if (mNextWriteTime == 0) {
            mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
        }

        notifyAll();
    }
    ....
    yieldIfQueueTooDeep();
}

 
3. yieldIfQueueTooDeep()方法。尝试暂停主线程,来为LazyTaskWriterThread提供线程执行机会。

private void yieldIfQueueTooDeep() {
    boolean stall = false;
    synchronized (this) {
        if (mNextWriteTime == FLUSH_QUEUE) {
            stall = true;
        }
    }
    if (stall) {
        Thread.yield();
    }
}

 
4. flush()方法。外部调用来立即写入的方法。

void flush() {
    synchronized (this) {
        mNextWriteTime = FLUSH_QUEUE;
        notifyAll();
        do {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        } while (mNextWriteTime == FLUSH_QUEUE);
    }
}

 
5. LazyTaskWriterThread。线程死循环执行,使用while (mWriteQueue.isEmpty())判断条件作为线程wait()条件,当外部notify()唤醒后向下执行,如果到下次执行时间会等待超时,再进行写入操作。

private class LazyTaskWriterThread extends Thread {

        LazyTaskWriterThread(String name) {
            super(name);
        }

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {

            // If mNextWriteTime, then don't delay between each call to saveToXml().
            final WriteQueueItem item;

            synchronized (TaskPersister.this) {
                if (mNextWriteTime != FLUSH_QUEUE) {
                    // The next write we don't have to wait so long.
                    mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
                }

                while (mWriteQueue.isEmpty()) {
                    if (mNextWriteTime != 0) {
                        mNextWriteTime = 0; // idle.
                        TaskPersister.this.notifyAll(); // wake up flush() if needed.
                    }

                    try {
                        TaskPersister.this.wait();
                    } catch (InterruptedException e) {
                    }
                    // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS from now.
                }
                item = mWriteQueue.remove(0);

                long now = SystemClock.uptimeMillis();
                while (now < mNextWriteTime) {
                    try {
                        TaskPersister.this.wait(mNextWriteTime - now);
                    } catch (InterruptedException e) {
                    }
                    now = SystemClock.uptimeMillis();
                }

                // Got something to do.
            }

            if (item instanceof ImageWriteQueueItem) {
                ....
            } else if (item instanceof TaskWriteQueueItem) {
                // Write out one task.
                ....
            }
        }
    }
}

 

五、总结

  1. 当在对象上调用wait()方法时,执行该代码的线程立即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁,如果线程仍然在完成同步代码,则线程在移出之前不会放弃锁。

  2. wait()方法会使得当前线程T进入obj的等待池并交出obj的synchronization,但线程中其它对象的synchronization仍会保持住在线程等待期间。等待池中的线程不会去竞争该对象的锁。

  3. wait()和notify()必须在synchronized语句块内使用,wait()的调用要在while循环中不断检查条件是否满足,当条件不满足时会继续走wait()方法。在任一时刻,只有一个线程可以持有对象obj的对象锁(object's monitor)。

  4. notifyAll()使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify()或notifyAll(),但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

  5. 被唤醒的所有线程都会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值