Java多线程研究03-线程的基本操作(notify,notifyAll,interrupt,join,sleep)

JAVA中的基本线程操作

这是前面给出的线程状态切换图例,可能还不能完全理解其中的切换条件,我们将详细介绍JAVA中如何进行这些线程状态的操作。

本文将讲解notify、notifyAll、interrupt、join和sleep等操作。

notifynotifyAll操作

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时,第二个参数才会起作用(纳秒是一秒的十亿分之一)。请看这个方法的源代码:

public final synchronized void join(long millis, int nanos) throws InterruptedException {

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    join(millis);
}

以上是JDK1.7中,关于join(millis, nanos)方法的源代码。从源代码中我们可以看出,只有当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。

总结

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

时间: 2024-08-11 07:44:33

Java多线程研究03-线程的基本操作(notify,notifyAll,interrupt,join,sleep)的相关文章

Java多线程学习8:wait()和notify()/notifyAll()

轮询 线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作. 想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处理?一个办法就是,B线程while(i == 10000),这样两个线程之间就有了通信,B线程不断通过轮训来检测i == 10000这个条件. 这样可以实现我们的需求,但是也带来了问题:CPU把资源浪费了B线程的轮询操作上,因为while操作并不释放CPU资源,导致了CPU会一直在这个线程中做判断操

Java多线程之后台线程不执行finally

后台线程不执行finally package wzh.daemon; import java.util.concurrent.TimeUnit; class ADaemon implements Runnable { @Override public void run() { try { System.out.println("Starting ADaemon"); TimeUnit.SECONDS.sleep(1); } catch (Exception e) { System.ou

Java多线程之后台线程

将线程设置成后台线程Daemons 主线程结果后,后台线程将自动结果. package wzh.test; import java.util.concurrent.TimeUnit; class SimpleDaemons implements Runnable{ @Override public void run() { try { while (true) { TimeUnit.MILLISECONDS.sleep(100); System.out.println(Thread.curren

java多线程同步以及线程间通信详解&amp;消费者生产者模式&amp;死锁&amp;Thread.join()(多线程编程之二)

本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: [java] view plain copy print? package com.zejian.test; /** * @author zejian * @time 2016年3月12日 下午2:55:42 * @decrition 模拟卖票线程 */ public class Ticket implements Runnable { //当前拥有的票数 private 

Java 多线程 编程 教程 线程的运行与控制

在多个线程同时运行时,就存在多个线程之间的调度控制.资源分配等.本节就来讲解线程使用中的一系列控制命令和使用方法 线程的启动start().join()与停止stop() 线程的休眠sleep().挂起yield 线程的同步synchronized 线程的同步锁机制:wait().notify()和notifyAll() start()启动线程,当线程创建之后,利用start方法启动线程.此外还可以利用join方法让线程立即执行 sleep()让当前线程暂停一段时间之后继续执行.sleep()方

【Java多线程通信】syncrhoized下wait()/notify()与ReentrantLock下condition的用法比较

转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6556925.html  一:syncrhoized使用同一把锁的多个线程用通信实现执行顺序的调度 我们知道,使用syncrhoized关键字修饰一个代码块.一个方式时,在代码块.方法执行完毕之前是不会释放掉所持有的锁的,在执行期间,其他申请这个锁的线程只能阻塞等待.这些等待的线程被安排在一个等待队列中. 现在,由于某种原因,在当前同步代码块A中需要另一个同步代码块B先执行完,然后A才能继续进行下去.而同步代

Java多线程总结之线程安全队列Queue

在Java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就是队列.Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列. 注:什么叫线程安全?这个首先要明确.线程安全的类 ,指的是类内共享的全局变量的访问必须保证是不受多线程形式影响的.如果由于多线程的访问(比如修改.遍历.查看)而使这些变量结构被破坏

Java多线程-新特性-线程池

Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定可靠的多线程程序,线程部分的新增内容显得尤为重要. 有关Java5线程新特征的内容全部在java.util.concurrent下面,里面包含数目众多的接口和类,熟悉这部分API特征是一项艰难的学习过程.目前有关这方面的资料和书籍都少之又少,大部分介绍线程方面书籍还停留在java5之前的知识层面上. 在Java5之

Java多线程系列--“JUC线程池”01之 线程池架构

概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介绍JUC的最后一部分的内容——线程池.内容包括:线程池架构图线程池示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509903.html 线程池架构图 线程池的架构图如下: 1. Executor 它是"执行者"接口,它是来执行任务的.准确的说,Executor提供了execute()接口来执行