JAVA并发编程——线程协作通信(二)

线程间的协作

在前面我们了解了很多关于同步(互斥锁)的问题,下面来看一下线程之间的协作。这里主要说一下Java线程中的join()、sleep()、yield()、wait()、notify()和notifyAll()方法。其中wait()、notify()和notifyAll()是线程间的协作的主要方法。

一、join()

join :让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,一直等到 B线程执行完为止,A才能得以继续执行。类似于生活中排队的时候,有人插队,一直等到前面插队的人的事情处理完才可以。

public class UseJoin {
	
    static class JumpQueue implements Runnable {
        private Thread thread;

        public JumpQueue(Thread thread) {
            this.thread = thread;
        }

        public void run() {
            try {
                thread.join();//调用传入线程的join方法,必须等这个方法返回后,当前线程才能继续执行
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread().getName() + " terminate.");
        }
    }

    public static void main(String[] args) throws Exception {
        Thread previous = Thread.currentThread();//现在previous是主线程
        for (int i = 0; i < 10; i++) {
            // 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
            Thread thread =
                    new Thread(new JumpQueue(previous), String.valueOf(i));
            System.out.println(previous.getName()+" jump a queue the thread:"
                    +thread.getName());
            thread.start();
            previous = thread;
        }

        SleepTools.second(2);//让主线程休眠2秒
        System.out.println(Thread.currentThread().getName() + " terminate.");
    }
}

可以看出,程序中定义了10个线程,将主线程作为参数传递给第一个线程,在run方法中调用了主线程的join方法,然后将第一个参数作为参数传递给第二个线程 ,在run方法中调用了第一个线程的join方法。。。所以,第一个线程要等到主线程执行完才能执行,第二个线程要等到第一个线程执行完才能执行。。。

main jump a queue the thread:0
0 jump a queue the thread:1
1 jump a queue the thread:2
2 jump a queue the thread:3
3 jump a queue the thread:4
4 jump a queue the thread:5
5 jump a queue the thread:6
6 jump a queue the thread:7
7 jump a queue the thread:8
8 jump a queue the thread:9
main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.
5 terminate.
6 terminate.
7 terminate.
8 terminate.
9 terminate.

二、sleep()

sleep:让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,出于sleep()中的线程也不会执行。因此sleep()方法常用来暂停线程执行。

前面有讲到,当调用了新建的线程的start()方法后,线程进入到就绪状态,可能会在接下来的某个时间获取CPU时间片得以执行,如果希望这个新线程必然性的立即执行,直接调用原来线程的sleep(1)即可。

ublic class ThreadTest {

    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                thread.start();
                try {
                    Thread.sleep(1);   // 使得thread必然能够马上得以执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

三、线程让步:yield()

Java线程中的Thread.yield( )方法,即线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。

yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

举个例子:
一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。 但是线程是有优先级的,优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,最终第一个上车的,也有可能是优先级最低的人。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。

四、wait()、notify()和notifyAll()

wait():导致当前线程等待并使其进入到等待阻塞状态。直到其他线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程。

notify():唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操作,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。

notifyAll():唤醒在此同步锁对象上等待的所有线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。

下面看一个例子:
在快递邮寄过程中,公里数大于100的时候更新城市,当城市为ShangHai的时候显示到站。在main方法中启动了三个检查里程数变化的线程,三个检查地点变化的线程。
以下是示例代码:

public class Express {
    public final static String CITY = "ShangHai";
    private int km;/*快递运输里程数*/
    private String site;/*快递到达地点*/

    public Express() {
    }

    public Express(int km, String site) {
        this.km = km;
        this.site = site;
    }

    /* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
    public synchronized void changeKm(){
        this.km = 101;
        notifyAll();
    }

    /* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
    public  synchronized  void changeSite(){
        this.site = "BeiJing";
        notifyAll();
    }

    public synchronized void waitKm(){
        while(this.km<=100){//公里数小于100不做处理
            try {
                wait();
                System.out.println("Check Km thread["+Thread.currentThread().getId()
                		+"] is be notified");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("the Km is "+this.km+",I will change db");
    }

    public synchronized void waitSite(){
        while(this.site.equals(CITY)){//快递到达目的地
            try {
                wait();
                System.out.println("Check Site thread["+Thread.currentThread().getId()
                		+"] is be notified");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("the site is "+this.site+",I will call user");
    }
}

public class TestWN {
    private static Express express = new Express(0,Express.CITY);

    /*检查里程数变化的线程,不满足条件,线程一直等待*/
    private static class CheckKm extends Thread{
        @Override
        public void run() {
        	express.waitKm();
        }
    }

    /*检查地点变化的线程,不满足条件,线程一直等待*/
    private static class CheckSite extends Thread{
        @Override
        public void run() {
        	express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<3;i++){
            new CheckSite().start();
        }
        for(int i=0;i<3;i++){
            new CheckKm().start();
        }

        Thread.sleep(1000);
        express.changeKm();//快递里程数变化
        express.changeSite();//快递地点变化
    }
}

Check Km thread[17] is be notified
the Km is 101,I will change db
Check Km thread[16] is be notified
the Km is 101,I will change db
Check Km thread[15] is be notified
the Km is 101,I will change db
Check Site thread[14] is be notified
the site is BeiJing,I will call user
Check Site thread[13] is be notified
the site is BeiJing,I will call user
Check Site thread[12] is be notified
the site is BeiJing,I will call user

五、wait()/notify()/nitifyAll()有关说明

1.、wait()方法执行后,当前线程立即进入到等待阻塞状态,其后面的代码不会执行。

2、notify()/notifyAll()方法执行后,将唤醒此同步锁对象上的线程对象,但是,此时还并没有释放同步锁对象,也就是说,如果notify()/notifyAll()后面还有代码,还会继续进行,知道当前线程执行完毕才会释放同步锁对象。

3、wait()/notify()/nitifyAll()完成线程间的通信或协作都是基于不同对象锁的,因此,如果是不同的同步对象锁将失去意义。同时,同步对象锁最好是与共享资源对象保持一一对应关系。

4、当wait线程唤醒后并执行时,是接着上次执行到的wait()方法代码后面继续往下执行的。

5、wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

6、调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的锁。

7、调用某个对象的notify()方法能够唤醒一个正在等待这个对象的锁的线程,如果有多个线程都在等待这个对象的锁,则只能唤醒其中一个线程。

8、调用notifyAll()方法能够唤醒所有正在等待这个对象的锁的线程。

9、wait()、notify()和notifyAll()这三个不是Thread类声明中的方法,而是Object类中声明的方法,当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法。WHY?由于每个对象都拥有锁,所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。

10、调用某个对象的wait()方法,当前线程必须拥有这个对象的锁,因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

11、调用某个对象的wait()方法,相当于让当前线程交出此对象的锁,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);

12、调用某个对象的notify()方法,当前线程也必须拥有这个对象的锁,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

13、notify()和notifyAll()方法只是唤醒等待该对象的锁的线程,并不决定哪个线程能够获取到锁。

14、一个线程被唤醒不代表立即获取了对象的锁,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。

六、调用yield() 、sleep()、wait()、notify()等方法对锁有何影响

yield() :不会释放锁的。
sleep():不会释放锁的。
wait():调用前持有锁,调用了wait方法以后,锁会被释放。
notify() :调用前持有锁,包含了notify()的方法结束以后,锁才会被释放。

你可能感兴趣的:(Java)