Java线程状态及线程通信

目录

1、线程状态

2、线程终止

2.1、interrupt()

2.2、使用volatile标志位

3、线程通信

3.1、等待/通知机制

3.2、suspend()/resume()

3.3、wait()/notify()

3.4、LockSupport.park()/LockSupport.unpark()

3.5、Thread.join()


线程是系统调度的最小单元,cpu会在多个线程之间进行上下文切换,多线程能够充分的利用cpu的运算能力,这点在多核cpu当中体现得更加明显。对于一个应用程序而言,操作系统会为其创建一个进程,同时在这个进程中存在多个线程,多个线程的交替执行使得程序能够正常工作。

1、线程状态

在Thread的内部类State中,明确定义出Java线程的6个状态,并且指出这个状态只是jvm中的线程状态,而不是真正意义上的操作系统中的线程状态。

  • NEW:线程刚创建出来,还没有调用thread.start();
  • RUNNABLE:调用thread.start(),包含就绪和运行两种状态
  • BLOCKED:线程阻塞状态,源码中注释写到此时的线程在等待监视器锁(synchronized进入重量级锁之后,会操作对象监视器)
  • WAITING:等待状态,需要其他线程进行作出指定动作才能够被唤醒
  • TIMED_WAITING:等待超时,能够在指定时间返回
  • TERMINATED:线程终止状态,表示当前线程已经执行完毕

通过下图可以表示各个线程状态之间的关系(来自《Java并发编程的艺术》):

实例代码如下:

package com.xiaohuihui.thread;

/**
 * @Desription: 多线程运行状态切换示例
 * @Author: yangchenhui
 */
public class Demo2 {

    public static void main(String[] args) throws Exception {
        // 第一种状态切换 - 新建 -> 运行 -> 终止
        System.out.println("#######第一种状态切换  - 新建 -> 运行 -> 终止################################");
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1当前状态:" + Thread.currentThread().getState().toString());
                System.out.println("thread1 执行了");
            }
        });
        System.out.println("没调用start方法,thread1当前状态:" + thread1.getState().toString());
        thread1.start();
        // 等待thread1执行结束,再看状态
        Thread.sleep(2000L);
        System.out.println("等待两秒,再看thread1当前状态:" + thread1.getState().toString());
        // thread1.start(); TODO 注意,线程终止之后,再进行调用,会抛出IllegalThreadStateException异常

        System.out.println();
        System.out.println("############第二种:新建 -> 运行 -> 等待 -> 运行 -> 终止(sleep方式)###########################");
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 将线程2移动到等待状态,1500后自动唤醒
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread2当前状态:" + Thread.currentThread().getState().toString());
                System.out.println("thread2 执行了");
            }
        });
        System.out.println("没调用start方法,thread2当前状态:" + thread2.getState().toString());
        thread2.start();
        System.out.println("调用start方法,thread2当前状态:" + thread2.getState().toString());
        // 等待200毫秒,再看状态
        Thread.sleep(200L);
        System.out.println("等待200毫秒,再看thread2当前状态:" + thread2.getState().toString());
        // 再等待3秒,让thread2执行完毕,再看状态
        Thread.sleep(3000L);
        System.out.println("等待3秒,再看thread2当前状态:" + thread2.getState().toString());

        System.out.println();
        System.out.println("############第三种:新建 -> 运行 -> 阻塞 -> 运行 -> 终止###########################");
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Demo2.class) {
                    System.out.println("thread3当前状态:" + Thread.currentThread().getState().toString());
                    System.out.println("thread3 执行了");
                }
            }
        });
        synchronized (Demo2.class) {
            System.out.println("没调用start方法,thread3当前状态:" + thread3.getState().toString());
            thread3.start();
            System.out.println("调用start方法,thread3当前状态:" + thread3.getState().toString());
            // 等待200毫秒,再看状态
            Thread.sleep(200L);
            System.out.println("等待200毫秒,再看thread3当前状态:" + thread3.getState().toString());
        }
        // 再等待3秒,让thread3执行完毕,再看状态
        Thread.sleep(3000L);
        System.out.println("等待3秒,让thread3抢到锁,再看thread3当前状态:" + thread3.getState().toString());

    }

}

2、线程终止

在Java中,我们可以调用thread.start()告诉jvm当前线程处于就绪状态,等待操作系统的执行调用,那么我们又如何终止一个线程呢?一般有两种方式可以终止线程。

2.1、interrupt()

Thread类中的stop()和interrupt()都可以终止线程,但是stop()是一个过期的方法,因为stop在执行的过程中会释放掉锁,可能会造成线程安全问题,所以推荐使用interrupt()方法。

interrupt()在执行的过程中有可能会抛出异常,在源码注释中有写到wait(long),join(long),sleep(long)可能会抛InterruptedException。

下面通过一段代码可以验证stop的安全性问题:

package com.xiaohuihui.thread;

/**
 * @Desription: 线程stop强制性中止,破坏线程安全的示例
 * @Author: yangchenhui
 */
public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        StopThread thread = new StopThread();
        thread.start();
        // 休眠1秒,确保i变量自增成功
        Thread.sleep(1000);
        // 暂停线程
        // thread.stop(); // 错误的终止
        thread.interrupt(); // 正确终止
        while (thread.isAlive()) {
            // 确保线程已经终止
        } // 输出结果
        thread.print();
    }

}


package com.xiaohuihui.thread;

/**
 * @Desription:
 * @Author: yangchenhui
 */
public class StopThread extends Thread {

    private int i = 0, j = 0;

    @Override
    public void run() {
        synchronized (this) {
            // 增加同步锁,确保线程安全
            ++i;
            try {
                // 休眠10秒,模拟耗时操作
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ++j;
        }
    }

    /**
     * 打印i和j
     */
    public void print() {
        System.out.println("i=" + i + " j=" + j);
    }

}

2.2、使用volatile标志位

利用volatile的可见性特性,也可以实现逻辑上的线程终止,代码如下:

package com.xiaohuihui.thread;

/**
 * @Desription: 使用volatile变量实现线程终止
 * @Author: yangchenhui
 */
public class Demo4 {

    public volatile static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            while (flag) {
                System.out.println("运行中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        Thread.sleep(3000);
        flag = false;
        System.out.println("程序运行结束");

    }

}

 

3、线程通信

线程开始运行之后,会拥有线程独有的空间,同时也会有线程共享的空间。虽然Java对象和成员变量是存在于堆内存中,属于共享变量,但是每个线程依然可以拥有一份拷贝到自己的私有空间,这样做的目的是提高程序运行的性能。所以如果线程间不能通信,那么也就无法感知到共享变量的变化,无法更新私有空间中的变量值。

线程间的通信就是为了解决共享变量的可见性问题,之前博客中的volatile和synchronized就是线程通信的一种方式

在我们来看其他的线程通信方式之前,我们先了解什么是等待/通知机制(生产者/消费者模型)。

3.1、等待/通知机制

其实Demo4就使用到了这一编程思想,通过一个被volatile修饰的公共变量,来进行两个线程之间的通信。一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。前者是生产者,后者就是消费者。

可以抽象为下面的伪代码:

while(!flag){

    thread.sleep(1000);
}

doSomething();

但是上面的代码存在一定的问题,如果休眠的时间过长,那么消息无法及时的进行处理,但是如果休眠时间太短,又存在过多消耗cpu的情况。使用Java的等待通知机制,能够解决这个问题。

将上述模式再抽取为等待/通知的经典范式(《Java编发编程的艺术》)

  • 等待方:

1、获取对象的锁。

2、如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。

3、条件满足则执行对应的逻辑。

synchronized(object){
    while(条件不满足){
        object.wait();
    }
    doSomething();
}

JDK推荐使用while方式判断条件,不推荐使用if,因为notify()、notifyAll()和unpark()在执行的过程中可能会存在伪唤醒的情况。

  • 通知方:

1、获得对象的锁。

2、改变条件。

3、通知所有等待在对象上的线程。

synchronized(object){
    改变条件
    object.notifyAll();
}

使用suspend()/resume();wait()/notify();LockSupport.park()/LockSupport.unpark()可以进行线程之间的通信,suspend()/resume()会引发线程死锁问题,是jdk的过期方法,不推荐使用。

3.2、suspend()/resume()

thread.suspend()会挂起线程,但是在这个过程中并不会释放锁,所以容易造成线程死锁。如果thread.resume()先于thread.suspend()也会导致线程被永久挂起。

3.3、wait()/notify()

wait()/notify()通过对象监视器实现,所以这两个方法都需要在同步代码块里面进行调用。如果不在同步代码块里面,会抛出IllegalMonitorStateException异常。跟suspend()/resume()一样,wait()/notify()也会有顺序要求,如果notify()在wait()之前,并且wait()没有传超时时间,那么wait的线程就会一直处于WAITING状态。调用wait()方法的时候会释放对象锁,所以notify()执行的时候才能够获取到对象锁。

  • 使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
  • 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
  • notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
  • notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。
  • 从wait()方法返回的前提是获得了调用对象的锁。
package com.xiaohuihui.lock;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Desription:
 * @Author: yangchenhui
 */
public class WaitNotifyDemo {

    static boolean flag = true;
    static Object lock = new Object();

    public static void main(String[] args) {

        Thread waitThread = new Thread(new Wait(), "WaitThread");
        waitThread.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread notifyThread = new Thread(new Notify(), "NotifyThread");
        notifyThread.start();

    }

    static class Wait implements Runnable {
        @Override
        public void run() {
            // 加锁
            synchronized (lock) {
                // 当条件不满足时,继续wait,同时释放lock的锁
                while (flag) {
                    System.out.println(Thread.currentThread() + "   flag_is_true.wait@  " +
                            new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 条件满足时完成工作
                System.out.println(Thread.currentThread() + "   flag_is_false.wait@     " +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));

            }
        }
    }

    static class Notify implements Runnable {
        @Override
        public void run() {
            // 加锁,拥有lock的Monitor
            synchronized (lock) {
                // 获取lock的锁,然后进行通知,通知的时候不会释放lock的锁
                // 直到当前线程释放了lock后,WaitThread才能够从wait()方法中返回
                System.out.println(Thread.currentThread() + "   hold_lock.notify@      " +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
                lock.notifyAll();
                flag = false;
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 再次加锁
            synchronized (lock) {
                System.out.println(Thread.currentThread() + "   hold_lock_again.sleep@     " +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

3.4、LockSupport.park()/LockSupport.unpark()

不管是suspend()/resume(),还是wait()/notify(),都会存在调用的先后顺序问题。但LockSupport.park()/LockSupport.unpark()不会存在先后顺序问题。在调用park的时候并不会释放对象锁,只是将当前线程挂起,如果是在同步代码块当中调用park方法,然后又使用同样的锁,再调用unpark(Thread thread)的时候就有可能进入死锁状态。

调用多次unpark()只会生效一次,当调用过一次park()之后,状态位就会被还原。park()和unpark()只是在操作状态位而已。

在Java 6中,LockSupport增 加 了park(Object blocker)、parkNanos(Object blocker, longnanos)和parkUntil(Object blocker, long deadline)3个方法,用于实现阻塞当前线程的功能,其中参数blocker是用来标识当前线程在等待的对象(以下称为阻塞对象),该对象主要用于问题排查和系统监控。

三种线程通信方式的示例代码如下:

package com.xiaohuihui.thread;

import java.util.concurrent.locks.LockSupport;

/**
 * @Desription: 三种线程协作通信的方式:suspend/resume、wait/notify、park/unpark
 * @Author: yangchenhui
 */
public class Demo5 {

    public static Object pizza = null;

    public static void main(String[] args) throws Exception {
        // 对调用顺序有要求,也要开发自己注意锁的释放。这个被弃用的API, 容易死锁,也容易导致永久挂起。
//        new Demo5().suspendResumeTest();
//		 new Demo5().suspendResumeDeadLockTest();
//		 new Demo5().suspendResumeDeadLockTest2();

        // wait/notify要求在同步关键字里面使用,免去了死锁的困扰,但是一定要先调用wait,再调用notify,否则永久等待了
//         new Demo5().waitNotifyTest();
//		 new Demo5().waitNotifyDeadLockTest();

        // park/unpark没有顺序要求,但是park并不会释放锁,所以在同步代码中使用要注意
//		 new Demo5().parkUnparkTest();
        new Demo5().parkUnparkDeadLockTest();

    }

    /**
     * 正常的suspend/resume
     */
    public void suspendResumeTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (pizza == null) {
                // 如果没包子,则进入等待
                System.out.println("1、进入等待");
                Thread.currentThread().suspend();
            }
            System.out.println("2、买到pizza,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个pizza
        Thread.sleep(3000L);
        pizza = new Object();
        consumerThread.resume();
        System.out.println("3、通知消费者");
    }

    /**
     * 死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码
     */
    public void suspendResumeDeadLockTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (pizza == null) {
                // 如果没pizza,则进入等待
                System.out.println("1、进入等待");
                // 当前线程拿到锁,然后挂起
                synchronized (this) {
                    Thread.currentThread().suspend();
                }
            }
            System.out.println("2、买到pizza,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个pizza
        Thread.sleep(3000L);
        pizza = new Object();
        // 争取到锁以后,再恢复consumerThread
        synchronized (this) {
            consumerThread.resume();
        }
        System.out.println("3、通知消费者");
    }

    /**
     * 导致程序永久挂起的suspend/resume
     */
    public void suspendResumeDeadLockTest2() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (pizza == null) {
                System.out.println("1、没pizza,进入等待");
                try {
                    // 为这个线程加上一点延时
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 这里的挂起执行在resume后面
                Thread.currentThread().suspend();
            }
            System.out.println("2、买到pizza,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个pizza
        Thread.sleep(3000L);
        pizza = new Object();
        consumerThread.resume();
        System.out.println("3、通知消费者");
        consumerThread.join();
    }

    /**
     * 正常的wait/notify
     */
    public void waitNotifyTest() throws Exception {
        this.wait();
        // 启动线程
        new Thread(() -> {
            synchronized (this) {
                while (pizza == null) {
                    // 如果没pizza,则进入等待
                    try {
                        System.out.println("1、进入等待");
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("2、买到pizza,回家");
        }).start();
        // 3秒之后,生产一个pizza
        Thread.sleep(3000L);
        pizza = new Object();
        synchronized (this) {
            this.notifyAll();
            System.out.println("3、通知消费者");
        }
    }

    /**
     * 会导致程序永久等待的wait/notify
     */
    public void waitNotifyDeadLockTest() throws Exception {
        // 启动线程
        new Thread(() -> {
            if (pizza == null) {
                // 如果没pizza,则进入等待
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                synchronized (this) {
                    try {
                        System.out.println("1、进入等待");
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("2、买到pizza,回家");
        }).start();
        // 3秒之后,生产一个pizza
        Thread.sleep(3000L);
        pizza = new Object();
        synchronized (this) {
            this.notifyAll();
            System.out.println("3、通知消费者");
        }
    }

    /**
     * 正常的park/unpark
     */
    public void parkUnparkTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            while (pizza == null) {
                // 如果没pizza,则进入等待
                System.out.println("1、进入等待");
                LockSupport.park();
            }
            System.out.println("2、买到pizza,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个pizza
        Thread.sleep(3000L);
        pizza = new Object();
        LockSupport.unpark(consumerThread);
        System.out.println("3、通知消费者");
    }

    /**
     * 死锁的park/unpark
     */
    public void parkUnparkDeadLockTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (pizza == null) {
                // 如果没pizza,则进入等待
                System.out.println("1、进入等待");
                // 当前线程拿到锁,然后挂起
                synchronized (this) {
                    LockSupport.park();
                }
            }
            System.out.println("2、买到pizza,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个pizza
        Thread.sleep(3000L);
        pizza = new Object();
        // 争取到锁以后,再恢复consumerThread
        synchronized (this) {
            LockSupport.unpark(consumerThread);
        }
        System.out.println("3、通知消费者");
    }

}

3.5、Thread.join()

如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。线程Thread除了提供join()方法之外,还提供了join(long millis)和join(longmillis, int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。

看看thread.join()源码如下:

/**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

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

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

由源码可以看到thread.join()也是基于wait()/notify()实现,当线程终止时,会调用线程自身的notifyAll()方法,会通知所有等待在该线程对象上的线程。下面为Thread.join()的示例代码:

package com.xiaohuihui.lock;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Desription:
 * @Author: yangchenhui
 */
public class WaitNotifyDemo {

    static boolean flag = true;
    static Object lock = new Object();

    public static void main(String[] args) {

        Thread waitThread = new Thread(new Wait(), "WaitThread");
        waitThread.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread notifyThread = new Thread(new Notify(), "NotifyThread");
        notifyThread.start();

    }

    static class Wait implements Runnable {
        @Override
        public void run() {
            // 加锁
            synchronized (lock) {
                // 当条件不满足时,继续wait,同时释放lock的锁
                while (flag) {
                    System.out.println(Thread.currentThread() + "   flag_is_true.wait@  " +
                            new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 条件满足时完成工作
                System.out.println(Thread.currentThread() + "   flag_is_false.wait@     " +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));

            }
        }
    }

    static class Notify implements Runnable {
        @Override
        public void run() {
            // 加锁,拥有lock的Monitor
            synchronized (lock) {
                // 获取lock的锁,然后进行通知,通知的时候不会释放lock的锁
                // 直到当前线程释放了lock后,WaitThread才能够从wait()方法中返回
                System.out.println(Thread.currentThread() + "   hold_lock.notify@      " +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
                lock.notifyAll();
                flag = false;
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 再次加锁
            synchronized (lock) {
                System.out.println(Thread.currentThread() + "   hold_lock_again.sleep@     " +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

(PS:实现原理待后续博客更新)

发布了11 篇原创文章 · 获赞 23 · 访问量 1175

猜你喜欢

转载自blog.csdn.net/yangchenhui666/article/details/104244349