java 线程并发中的等待与唤醒

2016-08-03 18:09:36   最后更新: 2016-08-03 18:09:36   访问数量:587




java 线程状态通常包括以下几种:

  1. 创建(new)
  2. 就绪(runnable)
  3. 运行(running)
  4. 阻塞(blocked)
  5. time waiting
  6. waiting
  7. 消亡(dead)

当需要新起一个线程来执行某个子任务时,就创建了一个线程

但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态

当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待

当得到CPU执行时间之后,线程便真正进入运行状态

线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)

当由于突然中断或者子任务执行完毕,线程就会被消亡

下图展示了线程的状态及状态的变化流转:

 

 

通常线程会一直处于 running 状态直到运行结束、销毁,本篇日志中,我们就来看看如何让线程进入 waiting 状态和 time waiting 状态

 

我们曾经介绍过 Thread 类在创建多线程应用中的用法:

java 创建多线程的三种方式 -- Runnable、Thread、Callable

Thread 类提供了一个静态的 sleep 方法用来让线程进入 time waiting 状态,睡眠一段时间以后自动唤醒

需要注意的是,调用 sleep 方法并不会使线程释放锁

 

package com.techlog.test.service; import org.springframework.stereotype.Service; /** * just for test * Created by techlog on 16/5/20. */ @Service public class ChannelThreadLocal { public static void main(String[] argv) { new Thread(new Thread1()).start(); try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("Sleep interrupted"); } new Thread(new Thread2()).start(); } private static class Thread1 implements Runnable { @Override public void run() { System.out.println("Thread1 run"); } } private static class Thread2 implements Runnable { @Override public void run() { System.out.println("Thread2 run"); } } }

 

 

可以看到,在输出 Thread1 run 后,等待了 3 秒后,才输出了 Thread2 run

 

在实际开发中,Object 类的三个用来实现线程同步的方法常常被用到,尤其在解决生产者-消费者问题的时候:wait、notify、notifyAll

package com.techlog.test.service; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * just for test * Created by techlog on 16/5/20. */ @Service public class PrintSortedThreadName { private static List<Long> threadIds; private static int index; private static boolean start; public static void main(String[] argv) { threadIds = new ArrayList<Long>(); final Object lock = new Object(); int concurrentcount = 10; start = false; index = 0; for (int i = 0; i < concurrentcount; i++) { new Thread(new Process(lock)).start(); } synchronized (lock) { Collections.sort(threadIds); start = true; lock.notifyAll(); } } private static class Process implements Runnable { private final Object lock; public Process(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { threadIds.add(Thread.currentThread().getId()); while (threadIds.get(index) != Thread.currentThread().getId() || !start) { try { lock.wait(); } catch (InterruptedException e) { System.out.println("wait interrupted"); } } System.out.println(Thread.currentThread().getId() + " : " + Thread.currentThread().getName()); index++; lock.notifyAll(); } } } }

 

 

上面的代码实现了一个简单的线程同步功能:按照线程 ID 从小到大的顺序输出线程名称

我们通过 main 方法创建了 concurrentcount 指定个数的线程

每一个线程都向 threadIds 写入了自己的 ThreadId,然后,调用 Object 的 wait 方法陷入等待(因为类共享的 start 变量没有被开启)

main 方法在此后将 threadIds 进行了排序,并打开了 start 开关,此后,waiting 状态的所有线程相继被唤醒,打印出了:

11 : Thread-0

12 : Thread-1

13 : Thread-2

14 : Thread-3

15 : Thread-4

16 : Thread-5

17 : Thread-6

18 : Thread-7

19 : Thread-8

20 : Thread-9

 

锁 lock 实现了两个目的:对共享数据(index、threadIds)的保护和线程同步

上面的代码很直观地展示出了 Object 对象的 wait 和 notifyAll 两个方法的用法

wait 方法让线程释放锁并陷入 waiting 状态,直到被唤醒,如果在 wait 方法中传递了可选的 timeout 参数,则线程会在相应时间后自动被唤醒,而 notifyAll 则唤醒在相应锁上所有处于 waiting 状态的线程

Object 还实现了 notify 方法,每次调用只唤醒一个在相应锁上所有处于 waiting 状态的线程,通常我们使用 notifyAll,如果将上面代码中的 notifyAll 换成 notify,则所有并发线程会陷入死锁

 






技术帖      技术分享      线程      sleep      thread      lock      wait      notify      notifyall     


京ICP备15018585号