Java多线程中notify,notifyAll,interrupt,join,sleep,yield的基本操作

Java多线程中notify,notifyAll,interrupt,join,sleep,yield的基本操作

一.Java中线程的生命周期
在这里插入图片描述
二.notify,notifyAll,interrupt,join,sleep的基本操作

(一)notify和notifyAll操作
notify方法的工作情况

package com.zczpeng.thread;

public class NotifyTheadTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        new Thread(new ParentThread()).start();
    }

}
class ParentThread implements Runnable{

    /**
     * 这个对象,为每个ChildThread对象所持有,模拟这个对象为所有线程对象进行独占的现象
     */
    public static final Object THREAD_LOCK = new Object();
    @Override
    public void run() {

        /**
         * 启动三个对THREAD_LOCK对象进行独立抢占的线程
         */
        for(int i = 0;i<3;i++){
            ChildThread cthread = new ChildThread();
            new Thread(cthread).start();
        }

        /**
         * 在synchronized这行加断点,保证这部分最后执行。
         */
        synchronized (THREAD_LOCK) {
            THREAD_LOCK.notify();
        }
    }

}

class ChildThread implements Runnable{

    @Override
    public void run() {

        Thread t = Thread.currentThread();
        long id = t.getId();
        System.out.println("线程"+id+"启动成功,等待执行...");
        synchronized (ParentThread.THREAD_LOCK) {
            try {
                ParentThread.THREAD_LOCK.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //说明线程被唤醒了
        System.out.println("线程"+id+"执行了.");
    }

}

以上两段代码中,ParentThread 类负责创建三个ChildThread类的对象,每一个ChildThread类的实例对象都持有ParentThread .THREAD_LOCK 对象的“钥匙”,并通过wait方法退出ParentThread .THREAD_LOCK 对象的独占状态(但是不归还锁)
然后我们通过ParentThread 类中的ParentThread .THREAD_LOCK .notify()方法解除阻塞状态:

synchronized (THREAD_LOCK) {
        THREAD_LOCK.notify();
}

在这里插入图片描述
实际上,我们只知道有三个ChildThread类的实例对象处于等待ParentThread .THREAD_LOCK对象的“锁芯”空闲;我们并不知道ParentThread .THREAD_LOCK.notify()方法会将ParentThread .THREAD_LOCK对象的“锁芯”(独占权)交给这三个线程的哪一个线程(这个决定过程是由操作系统完成的)。而且我们还知道,ParentThread .THREAD_LOCK.notify()方法只会唤醒等待ParentThread .THREAD_LOCK对象“锁芯”(独占权)的三个ChildThread类的实例对象中的一个。

notifyAll方法的工作情况
实际上理解了notify()方法的工作情况,就不难理解notifyAll()方法的工作情况了。接下来,同样是以上小节的代码,我们将ParentThread 类中的ParentThread .THREAD_LOCK.notify()方法,替换成ParentThread .THREAD_LOCK.notifyAll()方法。如下代码片段所示:

synchronized (THREAD_LOCK) {
            THREAD_LOCK.notifyAll();
        }

在这里插入图片描述
等待ParentThread .THREAD_LOCK对象锁的“锁芯”(独占权)的三个线程被依次唤醒(依次得到独占权)。

(二)interrupt信号
interrupt,单词本身的含义是中断、终止、阻断。当某个线程收到这个信号(命令)的时候,会将自生的状态属性置为“interrupted”,但是线程本身并不会立刻终止。程序员需要根据这个状态属性,自行决定如何进行线程的下一步活动。
在这里插入图片描述
上图是文章中已出现线程状态变化图,我们已经知道线程从创建后可以处于多种不同状态:就绪(可运行)、运行中、阻塞(等待中)、死亡。并不是线程处于任何状态,都可以接收interrupt信号。如果在收到interrupt信号时,线程处于阻塞状态(wait()、wait(time)或者sleep引起的),那么线程将会抛出InterruptedException异常:

当Thread收到interrupt信号时,可能的两种结果:要么其线程对象中的isinterrupt属性被置为true;要么抛出InterruptedException异常。注意,如果抛出了InterruptedException异常,那么其isinterrupt属性不会被置为true。

代码示例
下面我们通过一段测试代码,来说明interrupt信号的工作情况:

public class InterruptProcessor {

    public static void main(String[] args) throws Exception {
        // thread one线程
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                Thread currentThread = Thread.currentThread();
                while(!currentThread.isInterrupted()) {
                    /*
                     * 这里打印一句话,说明循环一直在运行
                     * */
                    System.out.println("Thread One 一直在运行!");
                }

                System.out.println("thread one 正常结束!" + currentThread.isInterrupted());
            }
        });

        // thread two线程
        Thread threadTwo = new Thread(new Runnable() {
            @Override
            public void run() {
                Thread currentThread = Thread.currentThread();
                while(!currentThread.isInterrupted()) {
                    synchronized (currentThread) {
                        try {
                            // 通过wait进入阻塞 
                            currentThread.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace(System.out);
                            System.out.println("thread two 由于中断信号,异常结束!" + currentThread.isInterrupted());
                            return;
                        }
                    }
                }

                System.out.println("thread two 正常结束!");
            }
        });

        threadOne.start();
        threadTwo.start();
        // 可以在这里打上端点,以保证threadOne和threadTwo完成了启动
        System.out.println("两个线程正常运行,现在开始发出中断信号");
        threadOne.interrupt();
        threadTwo.interrupt();
    }
}

上面的示例代码中,我们创建了两个线程threadOne和threadTwo。其中threadOne线程在没有任何阻塞的情况下一直循环运行(虽然这种方式在正式环境中不建议使用,但是这里我们是为了模拟这种线程运行状态),每循环一次都检测该线程的isInterrupt属性的值,如果发现值为true则终止循环;另一个threadTwo线程,在启动后马上进入阻塞状态,等待唤醒(实际上没有其他线程会唤醒它,以便模拟线程阻塞的状态)。

这时我们向两个线程发出interrupt信号。以下是两个线程的执行结果:
Thread One 一直在运行!
Thread One 一直在运行!
thread one 正常结束!true
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:503)
at test.thread.interrupt.InterruptProcessor$2.run(InterruptProcessor.java:34)
at java.lang.Thread.run(Unknown Source)
thread two 由于中断信号,异常结束!false
通过显示的结果,我们看到threadOne线程的isInterrupt属性被成功置为true,循环正常结束线程运行正常完成;而threadTwo线程由于处于wait()引起的阻塞状态,所以在收到interrupt信号后,抛出了异常,其isInterrupt属性依然是false;

(三)join操作
join操作会使两个线程的执行过程具有先后顺序,具体来说:如果A线程调用B线程的join操作,A线程就会一直等待(或者等待某个指定的时间长度),直到B线程执行完成后(死亡状态)A线程才会继续执行。如下图所示:
在这里插入图片描述
下面我们演示一段示例代码,在代码中我们创建了两个线程:一个是main方法执行的线程(记为mainThread),另一个是名叫joinThread的线程。接下来我们在mainThread中调用joinThread的join方法,让mainThread线程一直阻塞到joinThread线程执行结束后,再继续执行。

package com.zczpeng.thread;

public class JoinThread implements Runnable {

    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        long id = thread.getId();
        System.out.println("主线程" + id + "正在运行");
        Thread joinThread = new Thread(new JoinThread());
        joinThread.start();
        try {
            //如果没有这一行,则运行结果可能为结果1,有这一行,结果一定为结果2
            joinThread.join();
            System.out.println("主线程" + id + "运行結束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void run() {
        Thread currentThread = Thread.currentThread();
        long id = currentThread.getId();
        System.out.println("线程" + id + "启动成功,准备进入等待状态(5秒)");

        // 使用sleep方法,模型这个线程执行业务代码的过程
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }

        // 执行到这里,说明线程被唤醒了
        System.out.println("线程" + id + "执行完成!");
    }

}

以下是执行结果:
如果不加 join,运行结果1:
在这里插入图片描述
但是加上join后的运行结果2:
在这里插入图片描述
注意:调用join方法的线程,如果接收到interrupt信号,也会抛出InterruptedException异常。

join()、join(millis)和join(millis, nanos)
在Java的基本线程操作中,join()、join(millis)和join(millis, nanos),都可以实现针对目标线程的等待,而这三个方法的区别主要是在等待时间上:
● join:相当于调用join(0),即一直等待到目标线程运行结束,当前调用线程才能继续执行;

● join(millis, nanos):调用线程等待 millis毫秒 + nanos 纳秒 时间后,无论无论目标线程执行是否完成,当前调用线程都会继续执行;实际上这个join方法的描述并不准确:第二个参数nanos只是一个参考值(修正值),且只有大于等于500000时,第二个参数才会起作用(纳秒是一秒的十亿分之一)。

(四)sleep操作
sleep将“当前线程”进入阻塞状态,并且不会释放这个线程所占用的任何对象锁的独占状态。这里请注意:
● 当前线程,是指当前调用sleep方法的线程。而不是指被调用的目标线程。请看如下代码片段:

Thread currentThread = Thread.currentThread();
Thread joinThread = new Thread(new SleepThread());
joinThread.start();

joinThread.sleep(50000);

请问,并阻塞的线程是哪一个?currentThread还是joinThread?思考1秒钟,,,那些心里回答“joinThread”的同学,说明还是没有搞清楚sleep方法的含义:请一定注意“当前线程”,进入阻塞状态的一定是“currentThread”。这也是为什么sleep方法是静态方法。
● sleep方法也不像wait方法的执行效果那样:sleep方法不会释放当前线程所占用的对象锁独占模式。请再看如下代码片段:

public class Join2Thread implements Runnable {



    public static void main(String[] args) throws Exception {
        Thread joinThread1 = new Thread(new Join2Thread());
        joinThread1.start();
        Thread joinThread2 = new Thread(new Join2Thread());
        joinThread2.start();
    }

    @Override
    public void run() {
        // 使用sleep方法,模型这个线程执行业务代码的过程
        try {
            synchronized (Join2Thread.class) {
                Thread.sleep(Long.MAX_VALUE);
            }
        } catch (InterruptedException e) {
            LOGGER.error(e.getMessage(), e);
        }
    }
}

请问有几个线程可以获取Join2Thread.class对象锁的钥匙(独占Join2Thread.class对象锁的抢占权)?
当joinThread1进行对象锁检查的时候,发现Join2Thread.class对象没有其它任何线程独占,于是获得Join2Thread.class对象锁的独占权,并执行到“Long.MAX_VALUE”;由于sleep并不会释放这个joinThread1线程的任何对象锁独占权(肯定就不会释放Join2Thread.class对象锁的独占权);

那么当joinThread2线程进行Join2Thread.class对象锁独占权的检查是,发现它依然被joinThread1线程占有。所以joinThread2线程等待在边界区,无法获得Join2Thread.class对象锁的钥匙。
所以能够拥有Join2Thread.class对象锁钥匙的线程就只有一个:joinThread1。

(四)yield使用

public class YieldExcemple {

    public static void main(String[] args) {
        Thread threada = new ThreadA();
        Thread threadb = new ThreadB();
        // 设置优先级:MIN_PRIORITY最低优先级1;NORM_PRIORITY普通优先级5;MAX_PRIORITY最高优先级10
        threada.setPriority(Thread.MIN_PRIORITY);
        threadb.setPriority(Thread.MAX_PRIORITY);

        threada.start();
        threadb.start();
    }
}

class ThreadA extends Thread {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("ThreadA--" + i);
            Thread.yield();
        }
    }
}

class ThreadB extends Thread {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("ThreadB--" + i);
            Thread.yield();
        }
    }
}

运行结果1:

ThreadB–0
ThreadB–1
ThreadB–2
ThreadB–3
ThreadB–4
ThreadB–5
ThreadB–6
ThreadB–7
ThreadB–8
ThreadB–9
ThreadA–0
ThreadA–1
ThreadA–2
ThreadA–3
ThreadA–4
ThreadA–5
ThreadA–6
ThreadA–7
ThreadA–8
ThreadA–9

运行结果2:
ThreadB–0
ThreadA–0
ThreadB–1
ThreadA–1
ThreadA–2
ThreadA–3
ThreadA–4
ThreadA–5
ThreadA–6
ThreadA–7
ThreadA–8
ThreadA–9
ThreadB–2
ThreadB–3
ThreadB–4
ThreadB–5
ThreadB–6
ThreadB–7
ThreadB–8
ThreadB–9

Java线程中的Thread.yield( )方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。 yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。但是线程是有优先级的,优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,最终第一个上车的,也有可能是优先级最低的人。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。

总结

本文介绍了JAVA中线程的基本操作。包括了:notify、notifyAll、wait、join、sleep、interrupt等。通过这些操作,读者已经能够操作线程在多种不同状态下进行工作切换了。当然这些方法并不是JAVA中线程的全部操作,例如还有destroy、stop、resume等操作,但这些操作都已经被淘汰(因为它们不安全)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值