浅谈JAVA 线程状态中可能存在的一些误区

BLOCKED 和 WAITING 的区别

BLOCKED 和 WAITING 两种状态从结果上来看,都是线程暂停,不会占用 CPU 资源,不过还是有一些区别的

BLOCKED

等待 Monitor 锁的阻塞线程的线程状态,处于阻塞状态的线程正在等待 Monitor 锁进入 synchronized   Block 或者 Method ,或者在调用 Object.wait 后重新进入同步块/方法。简单的说,就是线程等待 synchronized 形式的锁时的状态

下面这段代码中, t1 在等待 t0 的锁释放(synchronized代码块执行完成),那么此时 t1 的状态就是 BLOCKED

Object lock = new Object();
Thread t0 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronized (lock){
            System.out.println("t0 acquire lock success");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
});
t0.start();
Thread.sleep(100);
Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronized (lock){
            System.out.println("t1 acquire lock success");
        }
    }
});
t1.start();
Thread.sleep(100);
System.out.println("t0 state: "+t0.getState());
System.out.println("t1 state: "+t1.getState());
System.out.println("done.");

//output
t0 acquire lock success
t0 state: TIMED_WAITING
t1 state: BLOCKED
done.
t1 acquire lock success

WAITING

等待中的线程状态,下面几个方法的调用会导致线程进入 WAITING 状态:

  • Object.wait()
  • Thread.join()
  • LockSupport.park()

WAITING 状态中的线程在等待其他线程执行某些操作,比如在某个对象上调用 Object.wait() 的线程正在等待另一个线程在该对象上调用 Object.notify() 或 Object.notifyAll()。为 Thread.join() 的线程正在等待指定的线程停止。
下面这段代码中,t0 在通过 synchronized 获取了 lock 对象的锁之后,进行了 wait 操作,导致 t0 进入 WAITING 状态:

Object lock = new Object();
Thread t0 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronized (lock){
            System.out.println("t0 acquire lock success");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
});
t0.start();
Thread.sleep(100);
System.out.println("t0 state: "+t0.getState());
System.out.println("done.");

//output
t0 acquire lock success
t0 state: WAITING
done.

区别

JAVA 中除了 synchronized Block/Method 的锁,还提供了 JUC 下的锁实现, juc.lock 下的锁功能更强大。比如支持中断,支持重入/非重入,公平/非公平等;但是 juc 下的锁和 synchronized 的实现可是不太一样的
比如下面这段代码,同样是等待锁,可是和synchronized等待锁的状态还不一样:

ReentrantLock reentrantLock = new ReentrantLock();
Thread t0 = new Thread(new Runnable() {
    @Override
    public void run() {
        reentrantLock.lock();

        System.out.println("t0 acquire lock success");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
t0.start();
Thread.sleep(100);
Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        reentrantLock.lock();
        System.out.println("t1 acquire lock success");
    }
});
t1.start();
Thread.sleep(100);
System.out.println("t0 state: "+t0.getState());
System.out.println("t1 state: "+t1.getState());
System.out.println("done.");

//output
t0 acquire lock success
t0 state: TIMED_WAITING
t1 state: WAITING
done.

同样是加锁,在 JUC 的锁实现下线程状态不太一样,所以在观察线程状态时,不止是 BLOCKED 的状态才是等待锁, WAITING/TIMEWAITING 的状态仍然可能是等待锁的状态
不过 JUC 下的锁实现,让线程暂停/等待的核心方法还是 LockSupport.park , jstack 对于 PARKING 形式的 WAITING 会有标注,所以在线程 stack 时还是能一眼看出来的:

//这里显示了等待类型
"Thread-0" #11 prio=5 os_prio=31 tid=0x00007f9308110000 nid=0x5c03 waiting on condition [0x0000700007fc3000]
   java.lang.Thread.State: WAITING (parking)//这里虽然是WAITING,但还是标注了是parking类型的
        at sun.misc.Unsafe.park(Native Method)

而 synchronized 形式的锁在 jstack 下的输出会有所区别:

//这里显示了等待类型为monitor
"Thread-1" #12 prio=5 os_prio=31 tid=0x00007f833d919800 nid=0x5a03 waiting for monitor entry [0x00007000035af000]
   java.lang.Thread.State: BLOCKED (on object monitor)//这里是BLOCKED状态,同时显示了monitor的归属

所以在观察线程状态时,需要注意Object.wait()这种WAITING和juc下锁导致的WAITING的区别

RUNNABLE 真的是 RUNNABLE 吗?

下面是一段 jstack 输出的例子,该线程现在正在执行 socketRead0 方法(Native),并且是 RUNNABLE 状态

"RMI TCP Connection(2)-192.xxx.xx.xx" daemon prio=6 tid=0x000000000a3e8800 nid=0x158e50 runnable [0x000000000adbe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(Unknown Source)
at java.net.SocketInputStream.read(Unknown Source)
at java.io.BufferedInputStream.fill(Unknown Source)
at java.io.BufferedInputStream.read(Unknown Source)
- locked (0x00000007ad784010) (a java.io.BufferedInputStream)
at java.io.FilterInputStream.read(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

但其实这里的 RUNNABLE 只是 JAVA 层面的线程状态,在操作系统或进程角度来看,该线程还是 WAITING 的状态; SocketInputStream 是一个 BIO 的实现,当没有收到数据(或者说没有准备好可读的数据)时会发生阻塞,可这个阻塞在JAVA线程状态里是 RUNNABLE 的状态,不过他并不会占用用户态的 CPU 时间片,内核在接受到数据后会结束这个阻塞

参考

https://blog.fastthread.io/2018/09/02/threads-stuck-in-java-net-socketinputstream-socketread0/

到此这篇关于浅谈JAVA 线程状态中可能存在的一些误区的文章就介绍到这了,更多相关JAVA 线程状态内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java多线程之线程池七个参数详解

    ThreadPoolExecutor是JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它提供了任务提交.线程管理.监控等方法. 下面是ThreadPoolExecutor类的构造方法源码,其他创建线程池的方法最终都会导向这个构造方法,共有7个参数:corePoolSize.maximumPoolSize.keepAliveTime.unit.workQueue.threadFactory.handler. public ThreadPoolExecutor(int corePoolS

  • Java线程数究竟设多少合理

    需求缘起 Web-Server通常有个配置,最大工作线程数,后端服务一般也有个配置,工作线程池的线程数量,这个线程数的配置不同的业务架构师有不同的经验值,有些业务设置为CPU核数的2倍,有些业务设置为CPU核数的8倍,有些业务设置为CPU核数的32倍. "工作线程数"的设置依据是什么,到底设置为多少能够最大化CPU性能,是本文要讨论的问题. 一些共性认知 在进行进一步深入讨论之前,先以提问的方式就一些共性认知达成一致. 1.提问:工作线程数是不是设置的越大越好? 回答:肯定不是的. 一

  • Java利用线程工厂监控线程池的实现示例

    ThreadFactory 线程池中的线程从哪里来呢?就是ThreadFoctory public interface ThreadFactory { Thread newThread(Runnable r); } Threadfactory里面有个接口,当线程池中需要创建线程就会调用该方法,也可以自定义线程工厂 public class ThreadfactoryText { public static void main(String[] args) { Runnable runnable=

  • Java线程实现时间动态显示

    本文实例为大家分享了Java线程实现时间动态显示的具体代码,供大家参考,具体内容如下 代码如下: import javax.swing.*; import java.awt.*; import java.util.Date; public class Test1 { public static void main(String[] args) { JFrame frame = new JFrame("我的窗口"); frame.setBounds(200,200,400,400); J

  • java的多线程高并发详解

    1.JMM数据原子操作 read(读取)∶从主内存读取数据 load(载入):将主内存读取到的数据写入工作内存 use(使用):从工作内存读取数据来计算 assign(赋值):将计算好的值重新赋值到工作内存中 store(存储):将工作内存数据写入主内存 write(写入):将store过去的变量值赋值给主内存中的变量 lock(锁定):将主内存变量加锁,标识为线程独占状态 unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量 2.来看volatile关键字 (1)启动两个线程

  • Java 用两个线程交替打印数字和字母

    前一段时间听马士兵老师讲课,讲到某公司的一个面试,两个线程,其中一个线程输出ABC,另一个线程输出123,如何控制两个线程交叉输出1A2B3C,由于本人多线程掌握的一直不是很好,所以听完这道题,个人感觉收获良多,这是一个学习笔记.这道题有多种解法,不过有些属于纯炫技,所以只记录常见的三种解法.首先看第一种 1. park 和 unpark package cn.bridgeli.demo;   import com.google.common.collect.Lists;   import ja

  • Java多线程面试题(面试官常问)

    进程和线程 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的.系统运行一个程序即是从一个进程从创建.运行到消亡的过程.在Java中,当我们启动main函数时其实就是启动了一个JVM的进程,而mian函数所在的线程就是这个进程中的一个线程,称为主线程. 线程是比进程更小的执行单位.一个进程在其执行的过程中可以产生多个线程.与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程都有自己的程序计数器.虚拟机和本地方法栈,所以系统在产生一个线程,或在各个线程之间切换工作是,

  • JAVA多线程抢红包的实现示例

    大体思路 红包的分发见JAVA作业--红包分发. 而抢红包要解决的是线程问题. 其实比较简单,设定好人数,每个人一个线程,每个线程执行一遍,有红包就抢,没有红包就抢不到,所以run函数中只要判断现在还有没有红包就可以了. 代码实现 import java.util.Random; import java.util.Scanner; public class Main { public static void main(String[] args) { int person_num, red_po

  • Java并发编程之线程之间的共享和协作

    一.线程间的共享 1.1 ynchronized内置锁 用处 Java支持多个线程同时访问一个对象或者对象的成员变量 关键字synchronized可以修饰方法或者以同步块的形式来进行使用 它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中 它保证了线程对变量访问的可见性和排他性(原子性.可见性.有序性),又称为内置锁机制. 对象锁和类锁 对象锁是用于对象实例方法,或者一个对象实例上的 类锁是用于类的静态方法或者一个类的class对象上的 类的对象实例可以有很多个,但是每个类只有

  • 基于Java网络编程和多线程的多对多聊天系统

    1.前言 程序实现基于星型结构(服务器接收来自各个客户端发送的信息,然后将信息传递给其他客户端界面并在其他客户端界面显示发送的信息) 2.类图 3.代码 客户端代码: package netProgram; import java.io.IOException; import java.net.Socket; import java.net.SocketAddress; public class Client implements ScreenInputInterface{ private So

  • Java结束线程的三种方法及该如何选择

    java常用的结束一个运行中的线程的方法有3中:使用退出标志,使用interrupt方法,使用stop方法. 1.使用退出标志 即在线程内部定义一个bool变量来判断是否结束当前的线程: public class ThreadSafe extends Thread { public volatile boolean exit = false; public void run() { while (!exit){ //do work } } public static void main(Stri

  • Java中内核线程理论及实例详解

    1.概念 内核线程是直接由操作系统内核控制的,内核通过调度器来完成内核线程的调度并负责将其映射到处理器上执行.内核态下的线程执行速度理论上是最高的,但是用户不会直接操作内核线程,而是通过内核线程的接口--轻量级进程来间接的使用内核线程.这种轻量级进程就是所谓的线程. 2.优点 由于内核线程的支持,每一个线程都是一个独立的单元,因此就算某一个线程挂掉了,也不会导致整个进程挂掉. 3.缺点 这种实现方式也存在局限性.由于是基于内核线程实现的,所以当涉及到线程的操作时(创建.运行.切换等)就涉及到系统

  • 教你如何使用Java多线程编程LockSupport工具类

    LockSupport类 用于创建锁和其他同步类的基本线程阻塞原语,此类与使用它的每个线程关联一个许可.如果获得许可,将立即返回对park的调用,并在此过程中消耗掉它:否则may会被阻止.调用unpark可使许可证可用(如果尚不可用).(不过与信号量不同,许可证不会累积.最多只能有一个.) 方法park和unpark提供了有效的阻塞和解阻塞线程的方法,这些线程不会遇到导致已弃用的方法Thread.suspend和Thread.resume无法用于以下问题:由于许可,在调用park的一个线程与试图

  • Java多线程下载网图的完整案例

    Java多线程下载网图案例 此案例依赖--文件操作工具类(FileUtils) 使用 apache 的commons-io包下的FileUtilsimportorg.apache.commons.io.FileUtils; 下载commons-io包 官方API文档 点击即可下载,然后导入IDEA的库中或者项目中. 导包 首先创建一个下载器 步骤: 1.新建一个download类 2.在类中建立一个下载方法 下载方法需要接收2个变量,一个是url下载地址,一个是name文件名称 3.在下载方法中

  • Java实现UDP多线程在线咨询

    本文实例为大家分享了Java实现UDP多线程在线咨询,供大家参考,具体内容如下 1.发送的线程 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import jav

随机推荐