文章目录
- 1. BIO NIO AIO
- 2. 多线程
- 3. 线程的生命周期和状态
- 4. sleep() ⽅法和 wait() ⽅法对比
- 5. 为什么 wait() ⽅法不定义在 Thread 中?
1. BIO NIO AIO
在Java中,BIO、NIO和AIO是针对网络编程的不同I/O模型:
BIO(Blocking I/O):传统的阻塞式I/O模型,它以阻塞的方式进行数据读写操作。当一个线程执行I/O操作时,会被阻塞,直到数据准备好或者操作完成。这种模型相对简单,但对并发处理能力较弱。
NIO(Non-blocking I/O):非阻塞式I/O模型,引入了选择器(Selector)和通道(Channel)的概念。使用NIO,可以通过一个线程处理多个通道的I/O操作,提升了并发处理能力。但需要手动检查是否有数据可用,必要时才进行读写操作。
AIO(Asynchronous I/O):异步I/O模型,也称为NIO.2。在AIO中,数据准备好后会自动通知应用程序进行读写操作,提供了更高级别的异步事件处理机制,可以有效地利用系统资源。
因此,在Java中,BIO适用于连接数较小且吞吐量要求不高的场景,NIO适用于连接数较多但每个连接的交互频率较低的场景,而AIO适用于连接数较多且每个连接的交互频率较高的场景。选择哪种I/O模型取决于应用程序的具体需求。
2. 多线程
程序计数器为什么是私有的? 程序计数器主要有下⾯两个作⽤:
-
字节码解释器通过改变程序计数器来依次读取指令,从⽽实现代码的流程控制,如:顺序执⾏、
选择、循环、异常处理。 -
在多线程的情况下,程序计数器⽤于记录当前线程执⾏的位置,从⽽当线程被切换回来的时候能
够知道该线程上次运⾏到哪⼉了。
需要注意的是,如果执⾏的是 native ⽅法,那么程序计数器记录的是 undefined 地址,只有执⾏的
是 Java 代码时程序计数器记录的才是下⼀条指令的地址。
所以,程序计数器私有主要是为了线程切换后能恢复到正确的执⾏位置。
虚拟机栈和本地⽅法栈为什么是私有的?
虚拟机栈: 每个 Java ⽅法在执⾏的同时会创建⼀个栈帧⽤于存储局部变量表、操作数栈、常量 池引⽤等信息。从⽅法调⽤直⾄执⾏完成的过程,就对应着⼀个栈帧在 Java 虚拟机栈中⼊栈和 出栈的过程。 本地⽅法栈: 和虚拟机栈所发挥的作⽤⾮常相似,区别是: 虚拟机栈为虚拟机执⾏ Java ⽅法 (也就是字节码)服务,⽽本地⽅法栈则为虚拟机使⽤到的 Native ⽅法服务。 在 HotSpot 虚 拟机中和 Java 虚拟机栈合⼆为⼀。 所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地⽅法栈是线程私有的。
3. 线程的生命周期和状态
线程在Java中具有生命周期和状态,主要包含以下几个状态:
新建(New):当创建一个线程对象时,线程处于新建状态。此时线程正在被初始化,但还未开始执行。
运行(Runnable):在新建状态后,调用线程的start()方法会使线程进入就绪状态,并且可以开始执行。处于就绪状态的线程可能正在等待CPU时间片,一旦获得CPU时间片,就可以开始执行。需要注意的是,一个正在运行的线程也是属于就绪状态的一种。
阻塞(Blocked):在某些情况下,线程可能会被阻塞并暂时停止执行,进入阻塞状态。这可能是因为线程在等待某个资源、等待输入/输出完成、或者由于调用了Thread类的sleep()方法而暂停执行。当等待的条件满足时,线程会从阻塞状态恢复到就绪状态。
等待(Waiting):线程进入等待状态表示线程暂停执行,直到其他线程显示地唤醒它。线程可以通过调用wait()方法进入等待状态,也可以调用join()方法使得线程等待另一个线程执行结束。
超时等待(Timed Waiting):与等待状态类似,但是可以设置等待时间。线程可以通过调用带有超时参数的方法,如Thread.sleep()、Object.wait()和Thread.join()来进入超时等待状态。
终止(Terminated):线程的生命周期最终会结束,进入终止状态。线程可以通过执行完run()方法的代码或者因异常而非正常退出来达到终止状态。
需要注意的是,这些状态并不是严格的线性顺序,线程可以在运行、阻塞、等待和超时等待之间切换。这种切换是由操作系统和Java虚拟机自动管理的,程序员通常无需显式控制。
4. sleep() ⽅法和 wait() ⽅法对比
sleep()方法和wait()方法是用于线程暂停执行的两种不同方式,它们之间有以下几点区别:
来源:
sleep()方法是Thread类的静态方法,可以直接通过Thread.sleep()调用。
wait()方法是Object类的实例方法,需要在使用前对目标对象进行锁定,并通过synchronized代码块或方法进行调用。
使用方式:
sleep()方法会让当前线程按指定的时间暂时休眠。它不会释放已经获得的锁,并且时间一到会自动唤醒。
wait()方法会让当前线程进入等待状态,并释放该对象上的锁。它需要在其他线程中调用notify()或notifyAll()方法来唤醒等待的线程。
所属类别:
sleep()方法属于Thread类,主要用于控制线程的睡眠时间。
wait()方法属于Object类,主要用于多个线程之间同步和通信。
应用场景:
sleep()方法常用于模拟耗时操作、定时任务等场景,以及简单的线程间暂停。
wait()方法常用于线程间协作,实现线程的等待、唤醒和通知机制,用于解决线程同步问题。
总的来说,sleep()方法是线程自身的方法,不释放锁且可直接调用,主要用于线程的睡眠;wait()方法是Object类的方法,需在同步块中调用,会释放锁并等待其他线程的唤醒,主要用于线程间通信和同步。
5. 为什么 wait() ⽅法不定义在 Thread 中?
wait()方法没有定义在Thread类中,而是定义在Object类中的原因有以下几点:
等待/通知机制:wait()方法是与notify()和notifyAll()方法配合使用的,它们一起构成了等待/通知机制(wait/notify mechanism)。这个机制允许线程在等待某个条件成立时暂停执行并释放锁,等待其他线程通过通知来唤醒它。
对象级别的操作:等待/通知机制需要应用于共享资源或者多个线程之间的同步和协作。将wait()方法定义在Object类中,使得任何一个Java对象都可以具备等待/通知的能力,而不仅仅局限于Thread类。
灵活性和扩展性:将wait()方法定义在Object类中可以提供更大的灵活性和扩展性。由于线程往往是基于对象进行操作的,可以在任何一个对象上调用wait()方法,并且通过notify()或notifyAll()方法将等待的线程唤醒,实现更细粒度的线程控制和协作。
总之,将wait()方法定义在Object类中是为了支持线程间的等待和通知机制,以及提供更加灵活和扩展的方式来实现线程的同步和协作。