关于多线程的状态切换过程中,线程的状态会有多种的切换,在早期的jdk版本中,线程之间的切换主要是通过join,sleep,wait,notify,notifyall等系列的函数来进行状态变迁的。

线程之间的切换状态如下图所示:



Java 两个线程之间调用_java


线程共包括以下5种状态。


1. 新建状态(New) 

线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。

2. 就绪状态(Runnable)

也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

3. 运行状态(Running) 

线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。

4. 阻塞状态(Blocked) 

阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:等待阻塞 :通过调用线程的wait()方法,让线程等待某工作的完成。

同步阻塞 :线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。

其他阻塞 :通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5.死亡状态(Dead) 

线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

 

这里面我们先专门讲解下wait(), notify(), notifyAll()方法,首先结合相应的代码来说:

来一个基于wait和notify的等待通知模型代码先:

消息体:


1/**
 2 * @author idea
 3 * @data 2019/3/17
 4 */
 5public class Message {
 6
 7    /**
 8     * 消息体
 9     */
10    private String msg;
11
12    public Message(String str){
13        this.msg=str;
14    }
15
16    public String getMsg() {
17        return msg;
18    }
19
20    public void setMsg(String str) {
21        this.msg=str;
22    }
23
24}

通知:


1/**
 2 * @author idea
 3 * @data 2019/3/17
 4 */
 5public class Notifier implements Runnable {
 6
 7    private Message msg;
 8
 9    public Notifier(Message msg) {
10        this.msg = msg;
11    }
12
13    @Override
14    public void run() {
15        String name = Thread.currentThread().getName();
16        System.out.println(name+" started");
17        try {
18            Thread.sleep(1000);
19            synchronized (msg) {
20                msg.setMsg(name+" waiter的名称");
21                //这里可以试下改成notify
22                msg.notifyAll();
23            }
24        } catch (InterruptedException e) {
25            e.printStackTrace();
26        }
27
28    }
29
30}


等待者:



1/**
 2 * @author idea
 3 * @data 2019/3/17
 4 */
 5public class Waiter implements Runnable {
 6
 7    private Message msg;
 8
 9    public Waiter(Message m) {
10        this.msg = m;
11    }
12
13    @Override
14    public void run() {
15        String name = Thread.currentThread().getName();
16        synchronized (msg) {
17            try {
18                System.out.println(name + " waiting to get notified at time:" + System.currentTimeMillis());
19                msg.wait();
20            } catch (InterruptedException e) {
21                e.printStackTrace();
22            }
23            System.out.println(name + " 获取到msg的消息信息了:" + System.currentTimeMillis());
24            //打印消息内容
25            System.out.println(name + " processed: " + msg.getMsg());
26        }
27    }
28
29}


测试类:


1/**
 2 * @author idea
 3 * @data 2019/3/17
 4 */
 5public class WaitNotifyTest {
 6
 7    public static void main(String[] args) {
 8        Message msg1 = new Message("process1 it");
 9        Waiter waiter = new Waiter(msg1);
10        new Thread(waiter, "waiter").start();
11
12        Waiter waiter1 = new Waiter(msg1);
13        new Thread(waiter1, "waiter1").start();
14
15        Notifier notifier = new Notifier(msg1);
16        new Thread(notifier, "notifier").start();
17        System.out.println("All the threads are started");
18    }
19
20}

打印结果:


1waiter waiting to get notified at time:1552817803526
2waiter1 waiting to get notified at time:1552817803526
3All the threads are started
4notifier started
5waiter1 获取到msg的消息信息了:1552817804528
6waiter1 processed: notifier waiter的名称
7waiter 获取到msg的消息信息了:1552817804528
8waiter processed: notifier waiter的名称


在讲解wait和notify,notifyall之前首先需要理解moniter的概念。

 

关于什么是moniter?

这个名词如果第一次听说,可能会比较迷。暂且可以理解为java虚拟机给每个对象的class字节码都设置了一个监听器Monitor

如果有了解过synchronized底层的实现方式,应该会对entry-setwait-set这两个名词比较熟悉。

 

Java 两个线程之间调用_java_02

我们可以把Monitor想象成一个保险箱,里面专门存放一些需要被保护的数据。Monitor每次只允许一个线程进入,当一个线程需要访问受保护的数据(即需要获取对象的Monitor)时,它会首先在entry-set入口队列中排队(这里并不是真正的按照排队顺序),如果没有其他线程正在持有对象的Monitor,那么它会和entry-set队列和wait-set队列中的被唤醒的其他线程进行竞争(即通过CPU调度),选出一个线程来获取对象的Monitor,执行受保护的代码段,执行完毕后释放Monitor,如果已经有线程持有对象的Monitor,那么需要等待其释放Monitor后再进行竞争。

 

再说一下wait-set队列。当一个线程拥有Monitor后,经过某些条件的判断(比如用户取钱发现账户没钱),这个时候需要调用Objectwait方法,线程就释放了Monitor,进入wait-set队列,等待Objectnotify方法(比如用户向账户里面存钱)。当该对象调用了notify方法或者notifyAll方法后,wait-set中的线程就会被唤醒,然后在wait-set中被唤醒的线程和entry-set中的线程一起通过CPU调度来竞争对象的Monitor,最终只有一个线程能获取对象的Monitor

 

wait()、notify()和notifyAll()都是Object类中的方法,在jdk中都是属于本地调用函数:

1public class Object {
 2    ...
 3    private transient int shadow$_monitor_;
 4    public final native void notify();
 5    public final native void notifyAll();
 6    public final native void wait() throws InterruptedException;
 7    public final void wait(long millis) throws InterruptedException {
 8        wait(millis, 0);
 9    }
10    public final native void wait(long millis, int nanos) throws InterruptedException;
11    ...
12}


wait()

使用wait方法会让当前线程处于一个等待的装填,知道另外的线程调用notify或者notifyAll之后才会唤醒。在等待期间,该线程必须要放弃掉自身的moniter,(这里是强制释放锁,与sleep正好相反,sleep会占用moniter),然后该线程会被放入到等待队列中,直到接收到notify或者notifyall指令才会被唤醒。

 

notify()

使用notify方法可以将等待队列中的正在等待这个对象的moniter的线程唤醒,将其加入到同步队列中进行moniter的抢夺。

 

notifyAll()

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

 

在旧版本的jdk中,线程之间的通信主要局限于Object类里面的notify和wait等系列方法,wait函数不能够单独使用,必须是在synchronized 下才能使用,而且wait()必须要通过notify()或者notifyAll()的方法才能进行唤醒,并且wait-set只有一个,过于单一,因此缺乏一定的灵活性。


除了使用wait,notify,notifyall之外来实现线程的唤醒和阻塞之外,还有其他的工具类可以提供类似的功能。例如说LockSupport里面的park和unpark函数,可以提供对于线程状态的控制功能。


代码案例:


1import java.util.concurrent.locks.LockSupport;
 2
 3/**
 4 * @author idea
 5 * @data 2019/3/17
 6 */
 7class ParkThread implements Runnable{
 8
 9
10    @Override
11    public void run() {
12        System.out.println("ParkThread is run");
13        //堵塞当前线程!
14        LockSupport.park();
15        System.out.println("ParkThread is over");
16    }
17}
18
19class UnParkThread implements Runnable{
20
21    private Thread thread;
22
23    public UnParkThread(Thread thread) {
24        this.thread = thread;
25    }
26
27    @Override
28    public void run() {
29        System.out.println("UnParkThread is run");
30        //解除堵塞该线程!
31        LockSupport.unpark(thread);
32        System.out.println("UnParkThread is over");
33    }
34}
35
36public class LockSupportDemo {
37
38
39    public static void main(String[] args) {
40        Thread parkThread=new Thread(new ParkThread());
41        System.out.println("-----------");
42        Thread unparkThread=new Thread(new UnParkThread(parkThread));
43        parkThread.start();
44        unparkThread.start();
45    }
46
47}

同时LockSupport也提供了很多灵活操作的api

1// 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。
 2static Object getBlocker(Thread t)
 3// 为了线程调度,禁用当前线程,除非许可可用。
 4static void park()
 5// 为了线程调度,在许可可用之前禁用当前线程。
 6static void park(Object blocker)
 7// 为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用。
 8static void parkNanos(long nanos)
 9// 为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。
10static void parkNanos(Object blocker, long nanos)
11// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
12static void parkUntil(long deadline)
13// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
14static void parkUntil(Object blocker, long deadline)
15// 如果给定线程的许可尚不可用,则使其可用。
16static void unpark(Thread thread)
17


Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,有了解过阻塞队列底层实现的朋友应该会知道,阻塞队列实际上是使用了Condition来模拟线程间协作。

  • Condition是个接口,基本的方法就是await()和signal()方法
  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition() 
  • 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
  • Conditon中的await()对应Object的wait();
  • Condition中的signal()对应Object的notify();
  • Condition中的signalAll()对应Object的notifyAll()。

上边我们讲解了基于wait、notify实现的等待、通知模式,但是这种模式的实现缺乏一定的灵活性,那么接下来的这段内容主要是依靠Condition来实现生产消费模式:

生产者队列:


1import java.util.concurrent.CountDownLatch;
  2import java.util.concurrent.locks.Condition;
  3import java.util.concurrent.locks.Lock;
  4import java.util.concurrent.locks.ReentrantLock;
  5
  6/**
  7 * @author idea
  8 * @data 2019/3/17
  9 */
 10public class ProductQueue<T> {
 11
 12    private final T[] items;
 13
 14    private final Lock lock = new ReentrantLock();
 15    private Condition notFull = lock.newCondition();
 16    private Condition notEmpty = lock.newCondition();
 17
 18    private int head;
 19    private int tail;
 20    private int count;
 21
 22    public ProductQueue(int maxSize) {
 23        items = (T[]) new Object[maxSize];
 24    }
 25
 26    public ProductQueue() {
 27        this(10);
 28    }
 29
 30    public void put(T t) {
 31        lock.lock();
 32        try {
 33            while (count == getCapacity()) {
 34                //有点类似于Object.wait
 35                //队列满了则不写入
 36                System.out.println("资源已生产完毕,材料不足了");
 37                notFull.await();
 38            }
 39            items[tail] = t;
 40            if (++tail == getCapacity()) {
 41                tail = 0;
 42            }
 43            count++;
 44            //有点类似于Object.notifyAll
 45            notEmpty.signalAll();
 46        } catch (Exception e) {
 47            e.printStackTrace();
 48        } finally {
 49            lock.unlock();
 50        }
 51    }
 52
 53
 54    public T take() {
 55        lock.lock();
 56        try {
 57            while (count == 0) {
 58                //相当于Object.wait
 59                System.out.println("资源已经抢夺完毕");
 60                notEmpty.await();
 61            }
 62            T out = items[head];
 63            //GC
 64            items[head] = null;
 65            if (++head == getCapacity()) {
 66                head = 0;
 67            }
 68            --count;
 69            notFull.signalAll();
 70            System.out.println("抢到了资源:");
 71            return out;
 72        } catch (Exception e) {
 73            e.printStackTrace();
 74            return null;
 75        } finally {
 76            lock.unlock();
 77        }
 78    }
 79
 80    public int size() {
 81        lock.lock();
 82        try {
 83            return count;
 84        } finally {
 85            lock.unlock();
 86        }
 87    }
 88
 89    public int getCapacity() {
 90        return items.length;
 91    }
 92
 93
 94    public static void main(String[] args) throws InterruptedException {
 95        ProductQueue<Integer> productQueue = new ProductQueue<>();
 96        Thread provider = new Thread(new Provider(productQueue));
 97        //验证队列满了是否可以再加入
 98        provider.start();
 99        CountDownLatch countDownLatch = new CountDownLatch(1);
100        Consumer[] consumers = new Consumer[100];
101        //验证队列为空是否可以进行消费
102        for (int i = 0; i < 100; i++) {
103            consumers[i] = new Consumer(productQueue, countDownLatch);
104            consumers[i].start();
105        }
106        System.out.println("消费者创建完毕");
107        countDownLatch.countDown();
108    }
109
110}

生产者:



1
 2
 3/**
 4 * @author idea
 5 * @data 2019/3/17
 6 */
 7public class Provider implements Runnable {
 8
 9    private ProductQueue<Integer> productQueue;
10
11    public Provider(ProductQueue productQueue) {
12        this.productQueue = productQueue;
13    }
14
15    @Override
16    public void run() {
17        while (true){
18            for(int i=0;i<10;i++){
19                System.out.println("------------生产了资源:"+i);
20                productQueue.put(i);
21            }
22            try {
23                Thread.sleep(100);
24            } catch (InterruptedException e) {
25                e.printStackTrace();
26            }
27        }
28
29
30    }
31}


消费者:



与Object里面的wait和notify相比,Condition里面还包含了很多有趣的知识点,由于篇幅有限,关于Condition在aqs中源码的实现部分,以后我会专门抽空来写一篇文章进行总结。