Java多线程

来源:互联网 发布:魔力学堂源码 编辑:程序博客网 时间:2024/06/16 09:24

这几个方法都是Object的方法,主要配合synchronized同步一起使用。这涉及到Java的锁机制,可以参考我的一篇博文 Java多线程 - 锁机制。

我们接下来看看这几个方法:

(1)wait

public final void wait()  throws InterruptedException,IllegalMonitorStateException

线程要调用Object实例对象的wait方法前,必须要先获取该Object实例对象锁,否则报IllegalMonitorStateException异常,所以wait方法必须在获取Object实例对象锁的同步块中调用。在线程调用Object实例对象的wait方法后,线程便进入“等待”状态,直到收到通知或者中断为止

// ① wait 实质在线程(如A)调用Object实例(如lockA)的wait方法后,线程(A)会进入实例对象(lockA)的等待池中,而等待池的线程处于wait状态,不会主动去获取Object实例(lockA)对象锁,等待notify或者notifyAll。所以线程(A)等待的不是Object实例(lockA)对象锁,等待的是有其他线程调用Object实例(lockA)的notify或者notifyAll通知。

例子:

public static class RunnableA implements Runnable {        private Object lockA;        public RunnableA(Object lockA) {            this.lockA = lockA;        }        @Override        public void run() {            synchronized (lockA) {                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.err.println("RunnableA before wait lockA");                try {                    // 必须在 synchronized (lockA) {} 内调用,线程会在这里阻塞,等待通知                    lockA.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }                // 在其他线程没有调用notify或者notifyAll方法之前,不会执行                System.err.println("RunnableA after wait lockA");            }        }    }

(2)notify

public final native void notify() throws IllegalMonitorStateException

和wait方法一样,调用之前一定要先获取Object实例对象锁,否则报IllegalMonitorStateException错误。
在线程调用Object实例对象notify方法后,会通知等待该Object实例对象锁的其他线程(也就是调用过该Object实例的wait方法处于等待的线程),获取到该Object对象锁的线程回继续执行wait方法后面的语句。
需要注意的是,在线程(A)调用Object实例的notify方法后,并不会立刻释放Object实例对象锁,synchronized同步块的代码会执行完了(跳出synchronized同步块)之后,线程(A)才会释放Object实例对象锁,线程(B,任意一个处于wait状态的线程)才能获取到Object实例对象锁,继续往下执行。
如果有多个线程(A,B,C)都处于wait状态,任意一个线程(如A)会收到通知,其他线程(B,C)的仍然处于wait状态。即使Object实例对象锁闲置了,其他线程(B,C)也不会主动改变状态,也不会主动去获取Object实例对象锁。除非有线程(A)调用了Object实例notify方法,wait的线程(B,C)才机会收到通知,继续执行。

// ② notify 实质 // 结合 ① wait 实质在线程(如B)调用Object实例(lockA)的notify方法后,收到通知的线程(A)会被移到实例对象(lockA)的锁池,在这个池的线程会竞争实例对象(lockA)锁。竞争到锁的线程也会被移出锁池,继续执行wait后面的语句。

例子:

public static class RunnableB implements Runnable {    private Object lockA;    public RunnableB(Object lockA) {        this.lockA = lockA;    }    @Override    public void run() {        synchronized (lockA) {            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.err.println("RunnableB notify lockA");            // 必须在 synchronized (lockA) {} 内调用            lockA.notify();            // 即使调用notify通知其他线程,这段代码还是会先执行,因为还没有释放lockA锁            System.err.println("RunnableB after notify lockA");        }    }}

在给出main方法:

public static void main(String[] args) {    // 实例对象    Object lockA = new Object();    // RunnableA和RunnableB持有同一个实例对象    Thread threadA = new Thread(new RunnableA(lockA));    Thread threadB = new Thread(new RunnableB(lockA));    // 启动threadA和threadB    threadA.start();    threadB.start();}

打印结果:

RunnableA before wait lockA     // RunnableA开始等待,并释放lockA实例对象锁RunnableB notify lockA          // RunnableB执行RunnableB after notify lockA    // RunnableB继续执行,释放lockA实例对象锁RunnableA after wait lockA      // RunnableA收到通知,获取到lockA实例对象锁,继续执行wait后面的语句

(3)notifyAll

public final native void notifyAll() throws IllegalMonitorStateException

和wait,notify方法一样,调用之前一定要先获取Object实例对象锁,否则报IllegalMonitorStateException错误。
在线程调用Object实例对象notifyAll方法后,会通知等待该Object实例对象锁的所有线程(也就是调用过该Object实例的wait方法处于等待的所有线程)。
根据notify的实质原理,有2个线程(A,B)调用了Object实例对象(lockA)的wait方法,此时,这2个线程(A,B)就被移到了Object实例对象(lockA)的等待池中。有个线程(C)调用了Object实例对象(lockA)的notifyAll方法,此时,处于wait状态的2个线程(A,B)都会被移到Object实例对象(lockA)的锁池中。那么这2个线程(A,B)就会开始竞争Object实例对象(lockA)锁。如果其中一个线程(A)争到了,执行wait后面的语句,跳出synchronized同步代码块,释放锁。此时的另一个线程(B)还在Object实例对象(lockA)的锁池中,因为Object实例对象(lockA)锁已经闲置了,线程(B)便可以获取Object实例对象(lockA)锁,也接着执行wait后面的语句。
所以在线程中调用Object实例对象的notifyAll方法,会让所有处于wait状态的线程都有机会竞争实例对象锁。

(4)wait(long)和wait(long,int)

这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。

(5)sleep和wait的区别

两者都可以让线程阻塞,但是有2个明显的区别:

  • sleep是Thread类的方法,而wait是Object类的方法,根本不是同一类的方法。

  • sleep方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep方法的过程中,线程不会释放对象锁。而当调用wait方法的时候,线程会放弃对象锁,进入等待此对象的等待池,只有针对此对象调用notify方法后本线程才进入对象锁池准备。

(6)巩固

下面再给出一个例子(网上搜的),巩固一下我们对wait和notify的使用:

public class MyThreadPrinter implements Runnable {         private String name;         private Object prev;         private Object self;         private MyThreadPrinter(String name, Object prev, Object self) {             this.name = name;             this.prev = prev;             this.self = self;         }         @Override        public void run() {             int count = 10;             while (count > 0) {                 synchronized (prev) {                     synchronized (self) {                         System.out.print(name);                         count--;                        self.notify();                     }                     try {                         prev.wait();                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                 }             }         }         public static void main(String[] args) throws Exception {             Object a = new Object();             Object b = new Object();             Object c = new Object();             MyThreadPrinter pa = new MyThreadPrinter("A", c, a);             MyThreadPrinter pb = new MyThreadPrinter("B", a, b);             MyThreadPrinter pc = new MyThreadPrinter("C", b, c);             new Thread(pa).start();          Thread.sleep(100);  //确保按顺序A、B、C执行          new Thread(pb).start();          Thread.sleep(100);            new Thread(pc).start();             Thread.sleep(100);        }     }    

我们来分析一下上面的例子。

(1)pa(启动或唤醒),先获取prev(c)对象锁(同时持有c对象锁是pc线程。第一次获取时,pc没有启动;第一次之后,pc早已经执行完synchronized(self)代码块,自然已经释放了c对象锁),然后获取self(a)对象锁(同时持有a对象锁是pb线程。第一次获取时,pb没有启动;第一次之后,pb调用a的wait方法,处于阻塞状态,释放了a对象锁),再调用self(a)的notify,唤醒pb(第一次启动时,pb并没有启动;第一次之后,pb处于wait状态,便收到通知,开始唤醒),最后调用prev(c)的wait方法,开始阻塞,等待prev(c)的notify或者notifyAll。

(2)pb(启动或唤醒),先获取prev(a)对象锁(同时持有a对象锁是pa线程。此时的pa处于wait状态,而且已经执行完synchronized(self)代码块,不再持有a对象锁),然后再获取self(b)对象锁(同时持有a对象锁是pc线程。第一次获取时,pc没有启动;第一次之后,pc调用b的wait方法,处于阻塞状态,释放b对象锁),再调用self(b)的notify,唤醒pc(第一次启动时,pc并没有启动;第一次之后,pc处于wait状态,便收到通知,开始唤醒),最后调用prev(a)的wait方法,开始阻塞,等待prev(a)的notify或者notifyAll。

(3)pc(启动或唤醒),先获取prev(b)对象锁(同时持有b对象锁是pb线程。此时的pb处于wait状态,而且已经执行完synchronized(self)代码块,不再持有b对象锁),然后再获取self(c)对象锁(同时持有c对象锁是pa线程。pa调用c的wait方法,处于阻塞状态,释放c对象锁),再调用self(c)的notify,唤醒pa(pa处于wait状态,便收到通知,开始唤醒),最后调用prev(b)的wait方法,开始阻塞,等待prev(b)的notify或者notifyAll。因为pa收到唤醒通知,有回到(1)。

所以运行的结果是ABCABCABCABC…

到这里,相信你也会很自如的使用wait和notify了。

参考资料

【Java并发编程】之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明