Dawn's Blogs

分享技术 记录成长

0%

Java面试之并发编程 (1) Java线程概述

Java 线程

多线程并不是银弹!在单核 CPU 上,如果线程是 CPU 密集型的,那么多个线程同时运行会导致频繁的线程切换,增加了系统的开销,降低了效率。如果线程是 IO 密集型的,那么多个线程同时运行可以利用 CPU 在等待 IO 时的空闲时间,提高了效率(线程数量也不能超过系统所能承载的上限)。

Java 线程和系统线程

在 Java 1.2 之前线程基于绿色线程(Green Thread)实现的,这是一种用户级别的线程;而从 Java 1.2 开始使用原生线程(Native Thread),这是内核级的线程,由操作系统内核进行线程的调度和管理。

在 Windows 和 Linux 等主流操作系统中,Java 线程采用的是一对一的线程模型,也就是一个 Java 线程对应一个系统内核线程。Solaris 系统是一个特例(Solaris 系统本身就支持多对多的线程模型),HotSpot VM 在 Solaris 上支持多对多和一对一。

一句话概括 Java 线程和操作系统线程的关系:现在的 Java 线程的本质其实就是操作系统的线程

Java 线程的生命周期

Java 线程的生命周期有六种不同的状态:

  1. NEW:初始状态,线程被创建出来但没有被调用 start() 方法。
  2. RUNNABLE:运行状态,线程被调用 start() 后,运行的状态。
  3. BLOCKED:阻塞状态,需要等待锁释放。
  4. WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
  5. TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
  6. TERMINATED:终止状态,表示该线程已经运行完毕。

Java 线程状态变迁图

在操作系统中,线程有就绪态和运行态,但是 Java 中将这两个状态合二为一称为 RUNNABLE 态,这是为什么呢?

因为现代操作系统基本使用时间片的方式进行线程调度,线程切换非常快速,区分这两种状态就没什么意义了。

创建线程的方法

创建线程有很多种方式,例如继承 Thread 类、实现 Runnable 接口、实现 Callable 接口、使用线程池、使用 CompletableFuture 类等等。

不过,这些方式其实并没有真正创建出线程。准确点来说,这些都属于是在 Java 代码中使用多线程的方法。

严格来说,Java 就只有一种方式可以创建线程,那就是通过 new Thread().start() 创建。不管是哪种方式,最终还是依赖于 new Thread().start()

如果直接调用 Thread 类的 run(),会直接在 main 线程下作为普通方法执行 run 中的内容,并不会开启多线程去执行。

如果开启多线程,只能通过 new Thread().start() 创建。

sleep 和 wait 方法

sleep 方法是 Thread 类对象方法,作用是让当前线程暂停执行。

而 wait 方法是 Object 对象方法,让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每一个对象都有对象锁,既然要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象而不是当前的线程。

异同点

共同点:两者都可以暂停线程的执行。

区别

  • sleep() 方法没有释放锁,而 wait() 方法释放了锁
  • wait() 通常被用于线程间交互/通信,sleep()通常被用于暂停执行。
  • wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout) 超时后线程会自动苏醒。
  • sleep()Thread 类的静态本地方法,wait() 则是 Object 类的本地方法。