线程的概念

线程是进程的子任务,一个进程可以创建多个线程,线程不拥有系统资源,但是线程可以共享进程的资源,而线程自己也有一块独立的小块空间:包括堆栈,程序计数器和局部变量。
线程是CPU调度和分派的基本单位。在同一时刻cpu只能执行一段代码,或者说叫一段顺序执行流,也就是线程。cpu在不同的线程之间来回切换,因为cpu的运行速度非常高,看起来就像这些线程一起执行一样,这就是并发。

线程状态

从不同的角度,会有不同的结果。
说到线程的状态,网上的说法众说纷纭,有说5种的,有说6种的,还有说7种的。具体怎么回事呢,得看是从什么角度来看。、
从操作系统来看: 线程可以分为5个状态

  1. 新建状态(NEW)
    线程的初始状态,还没有调用线程的start()方法
  2. 就绪状态(READY)
    线程启动之后,就进入就绪状态,等待cpu分配时间片执行。处于就绪状态的线程,只是说线程做好了cpu随时调用执行的准备
  3. 执行状态(RUNNING)
    cpu开始调度执行处于就绪状态的线程时,线程就是执行状态了。要想线程进入执行状态,它必须先是就绪状态
  4. 阻塞状态(BLOCKED)
    当处于运行状态的线程,由于某些原因暂时放弃cpu的使用权,则处于阻塞状态。直到其进入就绪状态,cpu才可以重新调度执行它。
  5. 终止状态(TERMINATED)
    线程执行完成或异常退出,线程终止。

而Java中,线程则分了6种状态。
新建和终止状态跟上面一样,就绪和执行状态,Java线程将这两种合并为一种:RUNNABLE:运行状态,表示线程已经在虚拟机中执行了。而阻塞状态,Java将之细分了3个状态: BLOCKED,WAITING,TIMEED_WAITING。
在Thread.State枚举中列举了这6种状态:

  1. 新建状态(NEW)
    Thread 创建之后的初始状态,还未调用start()方法,只是内存中的一个对象,操作系统并没有创建线程。
  2. 运行状态(RUNNABLE)
    线程已经在java虚拟机中执行,可能cpu调度已经正式开始运行,也有可能等待cpu调度。
  3. 阻塞状态(BLOCKED)
    在Java中,BLOCKED 状态跟内置锁synchronized(也叫监视器锁)关联,线程在等待获取其它线程持有的监视器锁进入同步代码块时,线程处于BLOCK状态。或者在线程调用wait方法之后等待重新进入同步代码块,也处于BLOCK状态。BLOCK只跟监视器锁有关,如果不需要进入同步代码块,而是因为其它的原因线程需要等待,不属于BLOCK.
  4. 等待状态(WAITING )
    一个线程由于调用了以下方法之一: Object.wait(),Thread.join(),LockSupport.park()而处于等待状态。
    处于WAITING状态的线程在等待其它线程执行特定的行为。例如:
    一个线程A调用了某个对象上的wait()方法,需要等待其它线程去执行对象上的notify()或者notifyAll()方法,线程A才会结束等待。
  5. 限时等待(TIMED_WAITING)
    一个线程由于调用了以下方法之一: Thread.sleep(long),Object.wait(long),Thread.join(long),LockSupport.parkNanos(long),LockSupport.parkUtil(long) 而等待。从这个命名上也说明是限时等待,不会无限等待下去,特定时间之后即使它等待的事件没有发生,也会退出等待状态继续执行。
    终止状态(TERMINATED)
    线程已经终止,包括正常执行完成或者异常退出。

线程通信: wait, notify, notifyAll, join

上面在RUNNABLE到WAITING,TIMED_WAITING状态的转换过程中,提到了wait,notify等方法。

wait

该方法是Object的一个实例方法,

/   ** @throws  IllegalMonitorStateException  if the current thread is not*               the owner of the object's monitor.* @throws  InterruptedException if any thread interrupted the*             current thread before or while the current thread*             was waiting for a notification.  The <i>interrupted*             status</i> of the current thread is cleared when*             this exception is thrown.* @see        java.lang.Object#notify()* @see        java.lang.Object#notifyAll()* /public final void wait() throws InterruptedException {wait(0);}

这也表明任何对象都可以调用wait方法:此时会释放对象的监视器锁,(监视器锁是内置锁的另一种称呼,两者是一样的),并且当前线程(标记为线程T)会保持休眠
直到发生一下四种情况之一:

  1. 其它线程调用该对象上的notify方法,并且当前线程正好被选中唤醒
  2. 其它线程调用该对象上notifyAll方法
  3. 其它线程中断了当前线程,当前线程的中断标志也会被清除
  4. 调用wait时传入了一个正数的等待时间,等待的时间过去

以上四种情况发生之后,线程T结束休眠,重新进入线程调度:它会按正常的方式与其他线程竞争锁。也就是说,休眠结束后,线程的同步状态跟调用wait前完全一样。
第3种情况中,wait方法会抛出InterruptedException异常,并且将线程的中断标志清除
第4种情况中,等待的时间过后如果线程在竞争中没有获得内置锁,后续代码也是无法执行的,超时时间达到仅仅是把线程从休眠状态中移出

调用wait方法的线程必须拥有对象的监视器锁,怎么拥有呢,有3种方式:
5. 调用对象的synchronized同步方法
6. 执行锁定在这个对象上的同步代码块
7. 对于Class对象,执行该类上的静态同步方法(static synchronized methodName)

这也跟Java内置锁 synchronized里的“获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法”说法一致。
如果线程没有获得这个对象的监视器锁呢,wait方法会抛出 IllegalMonitorStateException异常。

虚假唤醒

我们调用wait时,经常是需要判断某些条件的,例如当某某资源找不到时,调用wait方法。通常会这么写

synchronized(obj){
dosomething
if( condition not satisfy){obj.wait()
}

实际上并不推荐这么写,而是这样:

synchronized(obj){
dosomething
while( condition not satisfy){obj.wait()
}

就是因为虚假唤醒。一个线程也有可能在没有别的线程调用notify,nofifyAll,也没有中断,等待的时间也没有到的时候被唤醒,这叫做”虚假唤醒“,虽然在实践中很少出现,但是应用也要预防这种情况的发生。

notify 和 notifyAll

这两个方法也是Object的实例方法:

 /** @throws  IllegalMonitorStateException  if the current thread is not*               the owner of this object's monitor.* @see        java.lang.Object#notifyAll()* @see        java.lang.Object#wait()*/public final native void notify();/* @throws  IllegalMonitorStateException  if the current thread is not*               the owner of this object's monitor.* @see        java.lang.Object#notify()* @see        java.lang.Object#wait()*/public final native void notifyAll();

notify()方法唤醒一个正在该对象监视器锁的线程(调用了wait方法的线程),如果有多个线程在等待对象监视器锁,其中之一会被唤醒,具体唤醒哪个由java虚拟机决定。notifyAll()则会唤醒所有在该对象监视器上等待的线程,并且顺序是先进后出,最后一个进入休眠的线程会被首先唤醒

值得注意的是

调用notify(),notifyAll()时不会释放对象锁,而是notify所在的同步代码块结束之后才会释放锁,所以在调用notify,notifyAll方法之后,最好直接结束同步代码块。否则notify调用之后,锁不释放,被唤醒的线程也是在synchronized中的,获取不到对象锁也无法执行

notify,notifyAll 代码示例:

public static void main(String[] args) {Object service = new Object();Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread1 sleep 2s");ThreadUtil.sleep(2000);System.out.println("thread1 call wait");try {service.wait();System.out.println("thread1 wait finish");} catch (Exception e) {System.out.println("thread1 wait throw a exception");e.printStackTrace();}}}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread2 sleep 2s");ThreadUtil.sleep(2000);System.out.println("thread2 call wait");try {service.wait();System.out.println("thread2 wait finish");} catch (Exception e) {System.out.println("thread2 wait throw a exception");e.printStackTrace();}}}});Thread thread3 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread3 is running");service.notify();}}});thread1.start();thread2.start();ThreadUtil.sleep(2100); // 确保有一个线程调用了wait方法,如果没有线程调用wait方法而陷入等待,notify方法没有意义thread3.start();}执行结果:
thread1 sleep 2s
thread1 call wait
thread2 sleep 2s
thread2 call wait
thread3 is running
thread1 wait finish

thread1先执行,在调用wait之后释放对象锁进入等待,thread2获得对象锁,然后执行,也调用wait方法进入等待,thread3获得锁,调用notify方法唤醒某一个线程。thread1和thread2中一个会被唤醒,上面是thread1被唤醒:输出thread1 wait finish。也有可能是thread2被唤醒。notify只能唤醒一个线程,所以thread2的wait将会一直等待下去。
如果将thread3例的notify改为notifyAll。

thread2 sleep 2s
thread2 call wait
thread1 sleep 2s
thread1 call wait
thread3 is running
thread1 wait finish
thread2 wait finish

thread1和thread2都被唤醒了。

带时间参数的wait示例

public static void main(String[] args) {Object service = new Object();Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread1 call wait");try {service.wait();System.out.println("thread1 wait finish");System.out.println("thread1 sleep 10s");ThreadUtil.sleep(10000);} catch (Exception e) {System.out.println("thread1 wait throw a exception");e.printStackTrace();}}}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread2 call wait");try {service.wait(3000);System.out.println("thread2 wait finish");} catch (Exception e) {System.out.println("thread2 wait throw a exception");e.printStackTrace();}}}});Thread thread3 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("thread3 is running");service.notify();}}});thread1.start();thread2.start();ThreadUtil.sleep(100);thread3.start();}
执行结果:
thread1 call wait
thread2 call wait
thread3 is running
thread1 wait finish
thread1 sleep 10s
// 这里等了10秒,而不是3秒
thread2 wait finish

thread2调用了wait(3000),超时时间设置为3秒。在thread3调用notify之后,唤醒的是thread1进程,然后thread1调用sleep(10s),(sleep函数不会影响内置锁)内置锁依然在thread1手上。在3秒中的时候,thread2从休眠状态醒来,但是它没有获取到内置锁,也只能等待。直到10秒后,thread1执行完毕,释放内置锁,thread2取得锁才接着执行。

wait中被中断示例

public static void main(String[] args) {Object service = new Object();Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (service) {System.out.println("Thread.currentThread().isInterrupted(): "+Thread.currentThread().isInterrupted());System.out.println("thread1 call wait");try {service.wait();System.out.println("thread1 wait finish");System.out.println("thread1 sleep 10s");ThreadUtil.sleep(10000);} catch (Exception e) {System.out.println("thread1 wait throw a exception");System.out.println("exception Thread.currentThread().isInterrupted(): "+Thread.currentThread().isInterrupted());e.printStackTrace();}}}});Thread thread4 = new Thread(new Runnable() {@Overridepublic void run() {thread1.interrupt();}});thread1.start();ThreadUtil.sleep(100);thread4.start();}
结果:
Thread.currentThread().isInterrupted(): false
thread1 call wait
thread1 wait throw a exception
exception Thread.currentThread().isInterrupted(): false
java.lang.InterruptedExceptionat java.lang.Object.wait(Native Method)at java.lang.Object.wait(Object.java:502)at com.hz.MainTest$1.run(MainTest.java:85)at java.lang.Thread.run(Thread.java:748)

可以看到wait方法抛出了InterruptedException异常,并且线程的中断标志为false了。

join 方法

示例

join是Thread类的一个实例方法。
当前线程A调用另一个线程b的join()方法,会让当前线程A阻塞,直到线程b结束(包括正常结束或者异常退出)。
如果调用b.join(long)带时间参数的,则线程A会阻塞,但是如果时间达到b还没有结束,A也会结束阻塞。

public static void main(String[] args) throws Exception {Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println("thread1 sleep 10s");ThreadUtil.sleep(10000);} catch (Exception e) {e.printStackTrace();}}});thread1.start();thread1.join();System.out.println("main thread finish");执行结果:thread1 sleep 10s// 等待10秒main thread finish

主线程main会阻塞10秒,直到thread1结束,才输出 main thread finish
如果main中调用thread1.join(3000), 那主线程只会阻塞3秒。
为什么join 能达到这个效果呢。

我们以上文thread1.join()来分析一下源码

join方法源码

// 这是一个同步方法,先获得thread1的内置锁,thread1也是一个对象,也有内置锁
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()) { // 循环判断线程thread1是否还存活,存活则持续调用wait阻塞wait(0); // 这里的wait,是thread1.wait,而且是主线程调用的thread1.wait,阻塞的是主线程,thread1线程是正常执行的}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}

从上面的源码中,可以发现

  • join(long millis)方法的签名中,加了synchronized关键字来保证线程安全,join(long millis)最终调用的是Object对象的wait()方法,让主线程等待。这里需要注意的是,synchronized关键字实现的隐式锁,锁的是this,即thread这个对象,上例中就是thread1对象(因为synchronized修饰的join(long millis)方法是一个成员方法,因此锁的是实例对象),我们是在main线程中,调用了thread这个对象的join()方法,所以调用wait()方法时,调用的是thread这个对象的wait()方法,所以是main线程进入到等待状态中。(调用的是thread这个对象的wait()方法,这一点很重要,因为后面唤醒main线程时,需要用thread这个对象的notify()或者notifyAll()方法)
  • 既然是wait,那么必然有notify或者notifyAll。但是源码里并没有调用这两个方法,在哪里调用的呢,在jvm虚拟机中!
  • Java里面的Thread类在JVM上对应的文件是thread.cpp。thread.cpp文件的路径是jdk-jdk8-b120/hotspot/src/share/vm/runtime/thread.cpp
    在thread.cpp文件中定义了很多和线程相关的方法,Thread.java类中的native方法就是在thread.cpp中实现的。
    join方法是当目标线程执行完成之后,主线程继续执行。我们猜测,notifyAll方法是在thread的run方法执行完之后调用的,做一些收尾工作。
    在JVM中,当每个线程执行完成时,会调用到thread.cpp文件中JavaThread::exit(bool destroy_vm, ExitType exit_type)方法
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {assert(this == JavaThread::current(),  "thread consistency check");// ...省略了很多代码// Notify waiters on thread object. This has to be done after exit() is called// on the thread (if the thread is the last thread in a daemon ThreadGroup the// group should have the destroyed bit set before waiters are notified).// 关键代码就在这一行,从方法名就可以推断出,它是线程在退出时,用来确保join()方法的相关逻辑的。而这里的this就是指的当前线程。// 从上面的英文注释也能看出,它是用来处理join()相关的逻辑的ensure_join(this);
  • 可以看到,主要逻辑在ensure_join()方法中,接着找到ensure_join()方法的源码,源码如下

  • `static void ensure_join(JavaThread* thread) {
    // We do not need to grap the Threads_lock, since we are operating on ourself.
    Handle threadObj(thread, thread->threadObj());
    assert(threadObj.not_null(), “java thread object must exist”);
    ObjectLocker lock(threadObj, thread);
    // Ignore pending exception (ThreadDeath), since we are exiting anyway
    thread->clear_pending_exception();
    // Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
    java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
    // Clear the native thread instance - this makes isAlive return false and allows the join()
    // to complete once we’ve done the notify_all below
    java_lang_Thread::set_thread(threadObj(), NULL);

    // 核心代码在下面这一行
    // notify_all()方法肯定就是用来唤醒。这里的thread对象就是我们demo中的线程thread1这个实例对象
    lock.notify_all(thread);

既然这样,如果我提前将调用thread1的notifyAll()方法呢,那不就可以让主线程提前退出阻塞了么。于是我尝试了下面的代码

public static void main(String[] args) throws Exception {Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println("thread1 sleep 10s");ThreadUtil.sleep(10000);} catch (Exception e) {e.printStackTrace();}}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {ThreadUtil.sleep(1000);synchronized (thread1) {try {System.out.println("call notify");thread1.notifyAll();System.out.println("after notify");} catch (Exception e) {e.printStackTrace();}}}});thread1.start();thread2.start();thread1.join();System.out.println("main thread finish");}结果:
Connected to the target VM, address: '127.0.0.1:58744', transport: 'socket'
thread1 sleep 10s
call notify
after notify
Disconnected from the target VM, address: '127.0.0.1:58744', transport: 'socket'
// 这句话依然是10秒钟后输出的
main thread finish

我在thread2中先停1秒钟,然后调用thread1.notifyAll(),是希望在1秒钟后,主线程能结束阻塞,继续执行。但是不起作用! why?
再来看源码里,有这一句

 if (millis == 0) {while (isAlive()) { wait(0); }}

用的是while(isAlive()),而不是 if(isAlive()),thread1并没有结束,所以会再次调用wait阻塞。而且由于“虚假唤醒”的存在,调用wait时一般都是用while循环判断是否需要调用wait的,如下。

synchronized(obj){while (! condition){obj.wait()}
}

那我能不能把它改成if来验证呢,我本来想创建一个新的Thread子类,覆盖join方法,但是join方法是一个final方法,无法覆写,jdk也是考虑到这种情况吧

public final synchronized void join(long millis)

Java线程详解:wait、notify、notifyAll、join相关推荐

  1. java线程详解_Java线程详解

    程序.进程.线程的概念程序(program):是为完成特定任务.用某种语言编写的一组指令的集合.即指一段静态的代码,静态对象. 进程(process):是程序的一次执行过程,或是正在运行的一个程序.动 ...

  2. Java线程详解(深度好文)

    Java线程:概念与原理 一.进程与线程         进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间).进程不依赖于线程而独立存在,一个进程中可以启动多 ...

  3. Java线程详解(16)-条件变量

    条件变量是Java5线程中很重要的一个概念,顾名思义,条件变量就是表示条件的一种变量.但是必须说明,这里的条件是没有实际含义的,仅仅是个标记而已,并且条件的含义往往通过代码来赋予其含义. 这里的条件和 ...

  4. Java线程详解(13)-锁

    Java线程:新特征-锁(上) 在Java5中,专门提供了锁对象,利用锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制,这些内容主要集中在java.util.concurrent.lock ...

  5. Java线程详解(9)-并发协作

    Java线程:并发协作-生产者消费者模型 对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的.就像学习每一门编程语言一样,Hello World!都是最经典的例子. 实际上,准确说应 ...

  6. Java线程详解(8)-线程的同步

    Java线程:线程的同步-同步方法 线程的同步是保证多线程安全访问竞争资源的一种手段. 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问题, ...

  7. Java线程详解(6)-线程的交互

    线程交互是比较复杂的问题,SCJP要求不很基础:给定一个场景,编写代码来恰当使用等待.通知和通知所有线程. 一.线程交互的基础知识 SCJP所要求的线程交互知识点需要从java.lang.Object ...

  8. Java线程详解(7)-线程的调度

    Java线程:线程的调度-休眠 Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率. 这里要明确的一点,不管程序员怎么编写调度,只能最大限度的影响线程执 ...

  9. Java线程详解(18)-障碍器

    Java5中,添加了障碍器类,为了适应一种新的设计需求,比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候,就可以选择障碍器了. 障碍器是多线程并 ...

最新文章

  1. JDBC操作数据库实例
  2. 在Spring Boot中实现通用Auth认证的几种方式
  3. 福利继续:赠书《Spring Cloud微服务-全栈技术与案例解析》
  4. Web:你知道我这十几年是怎么过来的吗?!
  5. 中专生计算机教案,[定稿]计算机基础教案中专V8.1(全文完整版)
  6. [1424] 金克拉与贪吃蛇的故事
  7. 【es】es 分布式一致性原理剖析 节点篇
  8. UFT开发代码实例:将Excel中的数据保存为数组
  9. [Java] 内部类总结
  10. ios怎么引入masonry_iOS Masonry的使用需要注意的地方
  11. 神泣单机服务器维护,神泣单机版
  12. 嘉楠科技多位高管退出,知情人称管理层夺权内斗
  13. ImageIO 本地读取,网络下载图片
  14. java判断微信号、手机、名字的正则表达
  15. 魅族手机突然显示无服务器,魅族Flyme6是悟空请来的?Bug竟然有这么多?
  16. 笔记:Bootstrap导航与router-link 不和谐
  17. 用Oracle PL/SQL 编程实现小数转分数的方法
  18. idea package自动生成_Java idea使用generator自动生成mapper | Rickytsang洛水寒
  19. 信息系统项目管理师试题精选(四)
  20. [BFS]营救营救铁达尼号-C++

热门文章

  1. AutoJs学习-实现度娘传情
  2. 计算机组装硬件有哪些,电脑有哪些硬件 组装电脑怎么选择电脑硬件【介绍】...
  3. python爬取b站弹幕并进行数据可视化
  4. cookie的组成与作用
  5. SSL是什么?关于SSL和TLS的常见问题
  6. excel跨多个表格求和_一个小小的米字符号,在Excel中起到关键作用,搭配任何函数使用...
  7. 计算机怎么将硬盘分小,硬盘分区工具,小编教你怎么给电脑硬盘分区
  8. 【简易广告机】利用树莓派制作一个简易的广告机(1)
  9. 词云中去重复的词_【需要拿去】毕业论文结束语、致谢词(通用版)
  10. SDN被放弃?5G承载网:我一点也不慌!