工作线程

启动了一个jvm进程,main线程,RegisterClientWorker线程。
从上面代码就能看到,那个客户端类不是一个线程类,仅仅就写了一个方法,这个方法调用了工作线程。
main线程负责启动了RegisterClientWorker线程,其实干完这些事情以后,main线程就结束了,结束了以后但是jvm进程不会退出?为什么呢,有一个工作线程,就是RegisterClientWorker线程一直在运行。
所以jvm进程是不会退出的,会一直存在。只要有工作线程一直在运行,没有结束,那么jvm进程是不会退出的

Deamon线程

什么是daemon线程,什么是非daemon线程?
一般工作线程是非daemon线程,后台线程是daemon线程
默认创建的线程就是非daemon的,我们称之为工作线程。
如果你的main()方法启动之后就是要开启几个永久无限循环工作的线程,来处理一些请求之类的,比如web服务器那么那些线程就是工作线程。
但是java里还有一个daemon线程的概念,这个意思是说,如果jvm里的工作线程都停止了,比如main线程之类的都执行完了,那么daemon线程就会跟着jvm进程一起退出,不会像工作线程一样阻止jvm进程退出

ThreadGroup线程组(不常用)

ThreadGroup就是线程组,其实意思就是你可以把一堆线程加入一个线程组里,那关键这个玩意儿有啥好处?好处大概就是,你可以将一堆线程作为一个整体,统一的管理和设置。
实际上在java里,每个线程都有一个父线程的概念,就是在哪个线程里创建这个线程,那么他的父线程就是谁。举例来说,java都是通过main启动的,那么有一个主要的线程就是mian线程。在main线程里启动的线程,父线程就是main线程,就这么简单。
每个线程都必然属于一个线程组,默认情况下,你要是创建一个线程没指定线程组,那么就会属于父线程的线程组了,main线程的线程组就是main ThreadGroup。
在java中,线程都是有名字的,默认情况下,main线程的名字就叫main。其他的线程名字一般是叫做Thread-0之类的。
同时,也可以手动创建线程组,将线程加入这个创建的线程组中。就可以对这个线程组对象去做下面的操作,同时这些操作也会应用到线程组中的线程。

  • enumerate():复制线程组里的线程
  • activeCount():获取线程组里活跃的线程
  • getName()、getParent()、list(),等等
  • interrupt():打断所有的线程
  • destroy():一次性destroy所有的线程

    线程优先级

    设置线程优先级,理论上可以让优先级高的线程先尽量多执行,但是其实一般实践中很少弄这个东西,因为这是理论上的,可能你设置了优先级,人家cpu结果也还是没按照这个优先级来执行线程
    这个优先级一般是在1~10之间
    而且ThreadGroup也可以指定优先级,线程优先级不能大于ThreadGroup的优先级
    但是一般就是用默认的优先级就ok了,默认他会用父线程的优先级,就是5

    线程状态转换

    在 Java 中线程的生命周期中一共有 6 种状态。
  1. New(新创建)
  2. Runnable(可运行)
  3. Blocked(被阻塞)
  4. Waiting(等待)
  5. Timed Waiting(计时等待)
  6. Terminated(被终止)

    **

    Java线程基础 - 图2
    New 表示线程被创建但尚未启动的状态:当我们用 new Thread() 新建一个线程时,如果线程没有开始运行 start() 方法,所以也没有开始执行 run() 方法里面的代码,那么此时它的状态就是 New。而一旦线程调用了 start(),它的状态就会从 New 变成 Runnable,也就是状态转换图中中间的这个大方框里的内容。

    Runnable 可运行

    Java线程基础 - 图3
    Java 中的 Runable 状态对应操作系统线程状态中的两种状态,分别是 Running 和 Ready,也就是说,Java 中处于 Runnable 状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。
    所以,如果一个正在运行的线程是 Runnable 状态,当它运行到任务的一半时,执行该线程的 CPU 被调度去做其他事情,导致该线程暂时不运行,它的状态依然不变,还是 Runnable,因为它有可能随时被调度回来继续执行任务。

    阻塞状态

    Java线程基础 - 图4
    红框中的统称为阻塞状态,在 Java 中阻塞状态通常不仅仅是 Blocked,实际上它包括三种状态,分别是 Blocked(被阻塞)、Waiting(等待)、Timed Waiting(计时等待),这三 种状态统称为阻塞状态

    Blocked 被阻塞
    Java线程基础 - 图5

    从箭头的流转方向可以看出,从 Runnable 状态进入 Blocked 状态只有一种可能,就是进入 synchronized 保护的代码时没有抢到 monitor 锁,无论是进入 synchronized 代码块,还是 synchronized 方法,都是一样。
    当处于 Blocked 的线程抢到 monitor 锁,就会从 Blocked 状态回到Runnable 状态。

    Waiting 等待

    Java线程基础 - 图6
    线程进入 Waiting 状态有三种可能性。

  7. 没有设置 Timeout 参数的 Object.wait() 方法。

  8. 没有设置 Timeout 参数的 Thread.join() 方法。
  9. LockSupport.park() 方法。

刚才强调过,Blocked 仅仅针对 synchronized monitor 锁,可是在 Java 中还有很多其他的锁,比如 ReentrantLock,如果线程在获取这种锁时没有抢到该锁就会进入 Waiting 状态,因为本质上它执行了 LockSupport.park() 方法,所以会进入 Waiting 状态。同样,Object.wait() 和 Thread.join() 也会让线程进入 Waiting 状态。

Blocked 与 Waiting 的区别是 Blocked 在等待其他线程释放 monitor 锁,而 Waiting 则是在等待某个条件,比如 join 的线程执行完毕,或者是 notify()/notifyAll() 。

Timed Waiting 限期等待

Java线程基础 - 图7
在 Waiting 上面是 Timed Waiting 状态,这两个状态是非常相似的,区别仅在于有没有时间限制,Timed Waiting 会等待超时,由系统自动唤醒,或者在超时前被唤醒信号唤醒。
以下情况会让线程进入 Timed Waiting 状态。

  1. 设置了时间参数的 Thread.sleep(long millis) 方法;
  2. 设置了时间参数的 Object.wait(long timeout) 方法;
  3. 设置了时间参数的 Thread.join(long millis) 方法;
  4. 设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法。

    阻塞下的三种状态是如何转换到下一个状态的

    Java线程基础 - 图8
    想要从 Blocked 状态进入 Runnable 状态,要求线程获取 monitor 锁,而从 Waiting 状态流转到其他状态则比较特殊,因为首先 Waiting 是不限时的,也就是说无论过了多长时间它都不会主动恢复。
    Java线程基础 - 图9
    只有当执行了 LockSupport.unpark(),或者 join 的线程运行结束,或者被中断时才可以进入 Runnable 状态。
    Java线程基础 - 图10
    如果其他线程调用 notify() 或 notifyAll()来唤醒它,它会直接进入 Blocked 状态,这是为什么呢?因为唤醒 Waiting 线程的线程如果调用 notify() 或 notifyAll(),要求必须首先持有该 monitor 锁,所以处于 Waiting 状态的线程被唤醒时拿不到该锁,就会进入 Blocked 状态,直到执行了 notify()/notifyAll() 的唤醒它的线程执行完毕并释放 monitor 锁,才可能轮到它去抢夺这把锁,如果它能抢到,就会从 Blocked 状态回到 Runnable 状态。
    Java线程基础 - 图11
    同样在 Timed Waiting 中执行 notify() 和 notifyAll() 也是一样的道理,它们会先进入 Blocked 状态,然后抢夺锁成功后,再回到 Runnable 状态。
    Java线程基础 - 图12
    当然对于 Timed Waiting 而言,如果它的超时时间到了且能直接获取到锁/join的线程运行结束/被中断/调用了LockSupport.unpark(),会直接恢复到 Runnable 状态,而无需经历 Blocked 状态。

    Terminated 终止

    Java线程基础 - 图13
    要想进入这个状态有两种可能。
  • run() 方法执行完毕,线程正常退出。
  • 出现一个没有捕获的异常,终止了 run() 方法,最终导致意外终止。