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 线程的生命周期有六种不同的状态:
- NEW:初始状态,线程被创建出来但没有被调用
start()
方法。 - RUNNABLE:运行状态,线程被调用
start()
后,运行的状态。 - BLOCKED:阻塞状态,需要等待锁释放。
- WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
- TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
- TERMINATED:终止状态,表示该线程已经运行完毕。
在操作系统中,线程有就绪态和运行态,但是 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
类的本地方法。