【Java】Java核心要点总结 69

简介: 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操作,提升了并发处理能力。但需要手动检查是否有数据可用,必要时才进行读写操作。

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类中是为了支持线程间的等待和通知机制,以及提供更加灵活和扩展的方式来实现线程的同步和协作。

相关文章
|
安全 Java 数据库连接
【Java】Java核心要点总结:62
1. 线程中的线程是怎么创建的,是一开始就随着线程池的启动创建好的吗? 线程中的线程通常是通过在父线程中创建新的Thread对象并将其加入线程池中实现的。当然,这个过程也可以通过一些其他的方式来实现,比如使用ExecutorService.submit()方法提交一个Callable或Runnable任务。
|
缓存 安全 Java
【Java】Java核心要点总结70
1. volatile 如何保证变量的可⻅性? 在Java中,使用volatile关键字可以确保变量的可见性。 当一个线程修改了一个被volatile修饰的变量时,它会立即将该变量的最新值刷新到主内存。而其他线程在读取该变量时,会从主内存中重新获取最新值,而不是使用缓存中的旧值。 这样做的原因是,普通的变量在多线程环境下存在线程间不可见的问题。每个线程都有自己的工作内存,由于运行速度快和编译优化等原因,线程可能会直接读取工作内存中的旧值,而不去主内存中获取最新的值,导致线程之间的数据不一致。
|
2月前
|
消息中间件 NoSQL Java
Java知识要点及面试题
该文档涵盖Java后端开发的关键知识点,包括Java基础、JVM、多线程、MySQL、Redis、Spring框架、Spring Cloud、Kafka及分布式系统设计。针对每个主题,文档列举了重要概念及面试常问问题,帮助读者全面掌握相关技术并准备面试。例如,Java基础部分涉及面向对象编程、数据类型、异常处理等;JVM部分则讲解内存结构、类加载机制及垃圾回收算法。此外,还介绍了多线程的生命周期、同步机制及线程池使用,数据库设计与优化,以及分布式系统中的微服务、RPC调用和负载均衡等。
|
6月前
|
存储 自然语言处理 Java
Java基础之计算机基础知识引出Java
计算机基础知识概览:探讨计算机科学的约定性,指出其底层基于二进制逻辑运算,由晶体管开关控制。二进制与十进制间的转换是基础,计算机通过ASCII编码处理英文字符,但不包含汉字。早期计算机发展始于西方,从巴贝奇的差分机到图灵机,再到冯·诺伊曼结构。Unicode标准解决多语言字符编码,如UTF-8和UTF-16。编程语言分为低级(如汇编)和高级(如Java),以适应不同需求。
21 0
|
缓存 监控 Java
【Java】Java核心要点总结:61
1. java中的线程池是如何实现的 Java 中的线程池是通过 ThreadPoolExecutor 类实现的。ThreadPoolExecutor 继承自 AbstractExecutorService,并实现了 Executor、ExecutorService 和 Future 接口,它可以为程序提供管理和控制多个工作者线程的功能,提高线程重用性并避免线程频繁创建销毁的开销。
|
监控 安全 Java
Java面试题-Java核心基础
Java面试题-Java核心基础
104 2
|
存储 缓存 Java
【Java】Java核心要点总结 66
1. 成员变量 和 局部变量 的区别 ● 语法形式 :从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。 ● 存储方式 :从变量在内存中的存储方式来看,如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
|
算法 安全 Java
【Java】Java核心要点总结:59
1. 线程的run()和start()有什么区别,为什么不直接调用run() Java中通过继承 Thread 或实现 Runnable 接口来创建线程,线程是通过 start() 方法启动的,而不是直接调用 run() 方法。下面是它们之间的区别: start() 和 run() 的区别 调用 start() 方法会启动一个新线程并执行其中的 run() 方法,而直接调用 run() 方法将在当前线程中执行 run() 方法,并不会创建新的线程。
【Java】Java核心要点总结 68
1. 为什么重写 equals() 时候必须重写 hashCode() 因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。 如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。
|
存储 Java C语言
【Java】Java核心要点总结 67
1. 浮点数运运算会有精度损失 这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。