Java的NIO以及线程并发

简介:

一、NIO的出现

  NIO是JDK1.4里面才出现的东东,他给大家带来的最大好处是异步socket。其它file,pipe暂时就不多谈了。

  在JDK1.4出现之前,如果你需要编写一个Java服务器,为了实现异步操作,你必须为每个连接请求生成一个Java线程,当连接请求很多时,线程的调度,上下文切换,所付出的代价是非常昂贵,而且由于Java是跨平台的,各个平台对线程的支持并不相同,性能也不相同,因此传统的Java服务器编程架构是低效的且代价贵,dl大侠写了个util.concurrent包后,总算是减轻了线程调度给java程序员带来的痛苦,但是相比之与C、C++写出来的服务器,java服务器在性能要求很高的情况下,基本上没有什么竞争力,甚至是入围的权利的都没有。

  二、异步socket的实现

  NIO出现后,好像让java的程序员有了杨眉吐气的机会,怎么个吐气法,当时大家是个什么感受,俺是不知道,因为当时俺不搞java,对java的认识有限。

  NIO是一个基于事件的IO架构,最基本的思想就是:有事件我通知你,你再去做你的事情,没事件时你大可以节约大把时间去做其它任何事情。而且NIO的主线程only one,不像传统的模型,需要N个线程去,也减轻了JVM的工作量,使得JVM处理任务时显得更加高效。

  刚开始接触NIO时,被N层的Channel架构、网上铺天盖地的好评给镇住了,想想也应当是个很成熟的产品了,网上资料这么多,抄一抄Jetty、Tomcat以及其它一些牛B的源代码,基本上就能搞定了,此时没有想到大家受同步的影响这么深,也没有想到连最基本的异步概念都没有搞清楚就去写代码,搞出一堆的问题来(这是后话,后面再说)。

  现在研究了NIO以后,发现NIO实际上在Java中做的工作是很简单,就是将事件进行收集和分发,我们结合一个经典的调用例子来说明这个问题,我就不从NIO的基本使用说起了,大家可以查其它的资料,网上一大把。

  当Channel注册至Selector以后,我们的最经典的调用方法,是这样子的。

while(somecondition)
{

    int n = selector.select(TIMEOUT); 
    if(n == 0) continue; 
    for (Iterator iter = selector.selectedKeys().iterator(); iter.hasNext();) 
    { 
    if (key.isAcceptable()) 
        doAcceptable(key); 
    if (key.isConnectable()) 
        doConnectable(key); 
    if (key.isValid() && key.isReadable()) 
        doReadable(key); 
    if (key.isValid() && key.isWritable()) 
        doWritable(key); 
  iter.remove(); 
} 

}
  这只是个小例子啊,什么异常我就懒得抓了。

  nio中取得事件通知,就是在selector的select事件中完成的,在selector事件时有一个线程,这个线程具体的处理简单点说就是:向操作系统询问,selector中注册的Channel&&SelectionKey的偶对各种事件是否有发生,如果有则添加到selector的selectedKeys属性Set中去,并返回本次有多少个感兴趣的事情发生。程序员发现这个值>0,表示有事件发生,马上迭代selectedKeys中的SelectionKey,根据Key中的表示的事件,来做相应的处理。

 实际上,这段说明表明了异步socket的核心,即异步socket不过是将多个socket的调度(或者还有他们的线程调度)全部交给操作系统自己去完成,异步的核心Selector,不过是将这些调度收集、分发而已。因为操作系统的socket、线程调度再咋D也比你JVM中要强,效率也高。

  而且就算jvm做的和操作系统一样好,性能一样高(当然这是不现实的),使用异步socket你至少也节约了一半的系统消耗,想想假定操作系统本身也是使用线程来维护N个socket连接,在传统的java编程中,你还必须为这些socket还多起一个java线程,那至少是2N个线程,现在只需要N+1。在高并发的情况下,你自己去想吧。

  懂了这个道理,异步socket也就好写了,也不会搞得思路混乱了。

  三、异步Socket中应当注意的事情

  1、读

  异步socket最基本的理念就是事件通知,前面也说了,有事件通知你了,你才该做你应当做的事情。在异步socket中当注册了一个OP_READ事件后,你就等着Selector通知你吧,如果没有通知你,你在家睡大觉都行。

  在这里,我们有人出现的错误就是受同步的影响,自己去主动读,而且还搞出了多线程,如果仔细考虑一下,就不会出现这个问题了。同步socket中,调用read方法读取IO中的数据时,通常情况下如果没有数据read方法会阻塞,且是同步的,所以当多个线程同时访问时,read方法是线程安全的。

  而在异步下就不同,异步是不会阻塞的,有什么就返回什么,你主动去读,只要有数据,你就可以拿走,在多线程的情况下,也许你是想让第一个线程读取,but此时来数据时正好是线程2读到了,那线程2就高高兴兴的拿去,而线程1还在苦苦等待,这样导致数据混乱不说,如果后面再也不来数据了,线程1就是死循环啦。

  2、写

  在异步socket中,写是唯一一个主动点的操作,但是也不能直接去写Channel,而是应当先把自身注册为OP_WRITABLE,这时Selector就会发现你的存在,并把给发一个write事件,你这时后就可以写了,不过这时候有个小小的技巧,就是你执行写操作之前,请取消掉你的写注册,否则你的cpu肯定是100%。

  3、等待

  在传统的服务器编程中,由于对于每个请求都是产生的一个线程,因此你在你每个请求线程中wait也好,sleep也好,不会影响别人。但是异步不同,他的主线程只有一个,基本上每个处理都是线性的,也就是说处理完第一个,然后才能处理第二个,因此nio是一个极好的处理短连接的架构。

  我们现在出现的问题是,有人受同步的影响,没有搞清异步是如何处理,竟然在方法处理中用上sleep,而且一等还是3秒,这意味着什么,3秒才能处理一个请求,My god,我要一个3秒才能处理一个请求的服务器干嘛啊,还是60年代啊

  如果出现这样的需要等待的情况,应当另起一个线程(推荐使用线程池)去完成这个“长”时间的任务,或者将其它交给一个消息队列,通过发消息的方式将给别人去完成也行,客户端能等,你服务器怎么也能等呢?写出这样的代码,基本上一个服务器也就废了。

本文出自seven的测试人生公众号最新内容请见作者的GitHub页:http://qaseven.github.io/

目录
相关文章
|
1天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
3天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
3天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
3天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
17 3
|
3天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
18 3
|
3天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
38 2
|
10天前
|
存储 监控 Java
Java的NIO体系
通过本文的介绍,希望您能够深入理解Java NIO体系的核心组件、工作原理及其在高性能应用中的实际应用,并能够在实际开发中灵活运用这些知识,构建高效的Java应用程序。
26 5
|
11天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
43 6
|
26天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
24天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####