Java多线程wait,notify以及同步锁的运用实例

来源:互联网 发布:文字翻译语音软件 编辑:程序博客网 时间:2024/05/29 04:35

一次性开多个线程打印10次ABC的小Demo,如何保证线程执行的有序性,以下面这个Demo来做说明:

package com.lyt.usermanage.test;public class MyThreadTest5 implements Runnable {    private String name;    private Object lastThread;    private Object self;    public MyThreadTest5(String name,Object lastThread,Object self){        this.name = name;        this.lastThread = lastThread;        this.self = self;    }    @Override    public void run() {        int count  = 10;        while(count>0){            synchronized(lastThread){                synchronized(self){                    System.out.print(name);                    count --;                    /*唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,                    并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。                    调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。*/                    self.notify();                }                try {                    //调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。                    lastThread.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}
package com.lyt.usermanage.test;public class ThreadTest {    public static void main(String[] args) throws InterruptedException {        System.out.println(Thread.currentThread().getName()+"主线程运行开始!");        Object a = new Object();             Object b = new Object();             Object c = new Object();             MyThreadTest5 pa = new MyThreadTest5("A", c, a);             MyThreadTest5 pb = new MyThreadTest5("B", a, b);             MyThreadTest5 pc = new MyThreadTest5("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);     }}

先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,
主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,
那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。
一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。
主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,
两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,
终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,
持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。
看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。
但是这种假设依赖于JVM中线程调度、执行的顺序。

具体流程:

我们看到A B C三个线程共用三个锁对象a b c,由于在主线程加了休眠,那么三个线程的执行顺序就有了ABC的保障;A线程进来run方法后打印A,然后唤醒需要a对象同步锁的线程并释放持有的a对象锁,这个时候只有B线程在申请a锁,于是B线程被唤醒,然后A线程调用wait方法进入阻塞状态并释放c锁;B线程进行和A线程一样的步骤,进来后打印B然后释放b锁并唤醒等待b对象的线程,也就是C线程,然后调用wait方法进入阻塞状态并释放a对象;C线程重复上面的操作,唤醒了等待c锁对象的A线程,又是一个循环,因为new Thread(pa).start(); 方法里面不是公用的一个对象,所以各个线程都要跑10次,达到我们的需求10次ABC的输出。

个人理解是这样,如果有不足请大家多留言改进,技术只有交流才能深刻。

0 0
原创粉丝点击