Java基础学习(三):多线程、网络编程与新特性
Keep Team Lv4

Java基础学习(三):多线程、网络编程与新特性

Java作为一门成熟的编程语言,在多线程编程和网络通信方面提供了强大的支持,这两大领域是现代分布式系统和高性能应用的核心技术。同时,随着Java语言的持续演进,Lambda表达式、Stream API等新特性显著改善了开发体验。本文将深入探讨Java多线程模型、网络编程基础以及Java 8引入的重要新特性,帮助开发者掌握这些高级主题。

多线程编程基础

多线程编程允许程序同时执行多个任务,充分利用多核处理器的计算能力。在Java中,线程是程序执行的最小单元,每个线程都有独立的执行路径。Java线程模型基于操作系统原生线程,提供了平台无关的线程操作接口。

创建线程有多种方式。继承Thread类是最直接的方法,子类重写run()方法定义线程执行体。但这种方式限制了类的继承结构,因为Java不支持多重继承。实现Runnable接口是更灵活的选择,实现类定义run()方法,然后将Runnable实例传递给Thread构造器。这种方式允许实现类继承其他类,更适合大多数场景。Java 5之后,还可以实现Callable接口,与Runnable不同,Callable的call()方法可以返回值并抛出异常。

线程生命周期包含多个状态。新建状态是线程被创建但尚未启动。就绪状态是线程调用了start()方法,等待CPU调度。运行状态是线程获得CPU时间片正在执行。阻塞状态是线程等待某个条件(如I/O操作、锁获取)。等待状态是线程无限期等待其他线程的通知。超时等待状态是线程在指定时间内等待。终止状态是线程执行完毕或异常退出。理解这些状态转换对于调试多线程程序非常重要。

线程调度由JVM和操作系统共同管理。线程优先级是整数,范围从1到10,默认优先级为5。理论上优先级高的线程获得更多CPU时间,但实际行为依赖于操作系统调度器。不应该依赖线程优先级保证程序正确性。线程让步通过yield()方法提示调度器当前线程愿意让出CPU,但调度器可以忽略这个提示。线程休眠通过sleep()方法让当前线程暂停执行指定时间。

线程同步与协作

当多个线程访问共享资源时,可能发生竞态条件,导致数据不一致。线程同步机制确保多个线程安全地访问共享资源。Java提供了多种同步工具,每种工具适用于不同的场景。

synchronized关键字是Java最基本的同步机制,可以修饰方法或代码块。实例方法的synchronized锁是当前对象,静态方法的synchronized锁是类对象。synchronized代码块需要显式指定锁对象。synchronized基于内部锁(监视器锁)实现,获取锁的线程可以执行同步代码,其他线程必须等待。synchronized锁是可重入的,线程可以多次获取自己持有的锁。synchronized机制简单但功能有限,不支持尝试获取锁、锁超时等高级特性。

volatile关键字确保变量的可见性,但不保证原子性。当一个线程修改volatile变量时,新值立即对其他线程可见。volatile防止指令重排序,适用于状态标志等简单同步场景。但volatile不能用于复合操作,如i++,因为该操作包含读取、修改、写入三个步骤,可能被其他线程中断。

Java 5引入了java.util.concurrent包,提供了更强大的并发工具。ReentrantLock是synchronized的替代品,提供了更多功能:尝试获取锁、可中断锁、公平锁等。ReadWriteLock支持读写分离,允许多个读线程同时访问,但写线程独占访问。Condition对象提供了更灵活的等待/通知机制,替代Object的wait()/notify()方法。

线程间协作通过等待/通知机制实现。Object类的wait()、notify()、notifyAll()方法用于线程间通信。调用wait()的线程释放锁并进入等待状态,直到其他线程调用notify()或notifyAll()唤醒它。这些方法必须在同步代码块中调用,线程必须持有对象锁。典型的生产者-消费者模式使用这种机制协调线程活动。

并发集合是线程安全的集合实现,比同步包装集合性能更好。ConcurrentHashMap是线程安全的哈希表,支持高并发访问。CopyOnWriteArrayList在修改时创建底层数组的副本,适用于读多写少的场景。BlockingQueue是支持阻塞操作的队列,常用于生产者-消费者模式。

线程池与执行框架

直接创建和管理线程存在诸多问题:线程创建销毁开销大、资源消耗无限制、缺乏统一管理。线程池解决了这些问题,它维护一组工作线程,重复利用线程执行多个任务。

Executor框架提供了线程池的抽象。Executor接口定义了执行任务的抽象方法。ExecutorService扩展了Executor,添加了生命周期管理和任务跟踪功能。ScheduledExecutorService支持延迟执行和定期执行。Executors工厂类提供了创建各种线程池的静态方法。

Java提供了多种预定义线程池。固定大小线程池维护固定数量的线程,适用于负载较稳定的场景。缓存线程池根据需要创建新线程,空闲线程保留60秒,适用于短期异步任务。单线程执行器使用单个工作线程,保证任务按顺序执行。调度线程池支持延迟执行和定期执行。

线程池配置需要考虑多个因素。核心线程数是线程池保持的最小线程数。最大线程数是允许创建的最大线程数。工作队列存储等待执行的任务。线程存活时间是非核心线程空闲时的存活时间。拒绝策略处理无法接受的新任务。合理的配置需要根据任务特性、系统资源和性能要求权衡。

Future接口表示异步计算的结果。ExecutorService的submit()方法返回Future对象,可以通过它查询任务状态、取消任务、获取结果。Future的get()方法阻塞直到计算完成。Callable接口用于需要返回值的任务,与Runnable不同,Callable可以抛出异常。

网络编程基础

Java网络编程基于TCP/IP协议栈,提供了不同抽象级别的API。Socket是网络通信的基本抽象,表示网络连接的一端。Java支持TCP和UDP两种传输协议,每种协议适用于不同的应用场景。

TCP提供可靠的、面向连接的字节流服务。ServerSocket类用于服务器端监听连接请求。Socket类用于客户端发起连接和两端数据传输。TCP连接建立需要三次握手,断开需要四次挥手。TCP保证数据按序到达、无差错、不丢失,但开销较大。适用于需要可靠传输的场景,如文件传输、Web应用。

UDP提供不可靠的、无连接的数据报服务。DatagramSocket类用于发送和接收数据报。DatagramPacket类表示数据报,包含数据和目标地址信息。UDP不保证数据到达、不保证顺序、不保证不重复,但开销小、延迟低。适用于实时性要求高、允许少量丢失的场景,如视频流、在线游戏。

URL类提供了统一资源定位符的高级抽象。URLConnection类建立了应用程序与URL之间的通信链接。这两个类简化了基于标准协议(如HTTP、FTP)的网络编程,隐藏了底层Socket细节。HttpURLConnection是URLConnection的子类,专门用于HTTP协议,支持GET、POST等方法。

网络编程涉及多个重要概念。IP地址标识网络中的主机,端口号标识主机中的进程。DNS将域名解析为IP地址。防火墙可能限制网络访问。NAT(网络地址转换)允许私有网络访问公共网络。理解这些概念对于诊断网络问题非常重要。

Lambda表达式

Lambda表达式是Java 8引入的最重要特性之一,它使Java支持函数式编程风格。Lambda表达式本质上是一个匿名函数,可以作为参数传递或赋值给变量。

Lambda表达式语法简洁,由参数列表、箭头符号和函数体组成。参数类型可以省略,编译器可以推断类型。单个参数时括号可以省略。函数体可以是表达式或代码块。Lambda表达式没有自己的作用域,可以访问外部作用域的final或等效final变量。

函数式接口是只有一个抽象方法的接口,Lambda表达式可以实现函数式接口。Java提供了多个内置函数式接口。Predicate接受一个参数返回布尔值,用于测试条件。Function<T,R>接受一个参数返回结果,用于转换操作。Consumer接受一个参数无返回值,用于消费操作。Supplier无参数返回结果,用于提供值。这些接口在Stream API和并发编程中广泛使用。

方法引用是Lambda表达式的简洁写法,用于直接引用已有方法。静态方法引用使用类名::方法名。实例方法引用使用实例::方法名。特定类型任意对象方法引用使用类名::实例方法名。构造器引用使用类名::new。方法引用使代码更加简洁、意图更清晰。

Stream API

Stream API是Java 8引入的另一个重要特性,提供了声明式处理数据集合的能力。Stream不是数据结构,而是对数据源的元素序列进行函数式操作。

Stream操作分为中间操作和终端操作。中间操作返回新的Stream,支持链式调用,操作是惰性的,只有终端操作时才会执行。常见的中间操作包括filter()过滤元素、map()转换元素、sorted()排序元素。终端操作产生结果或副作用,触发实际计算,包括forEach()遍历元素、collect()收集元素、reduce()归约元素。

Stream处理流程遵循固定模式:获取Stream、执行中间操作、执行终端操作。数据源可以是集合、数组、I/O通道等。中间操作可以无限组合,但要注意操作顺序影响性能。终端操作只能有一个,执行后Stream不能再使用。

并行Stream利用多核处理器并行处理数据。通过parallel()方法将顺序Stream转换为并行Stream,通过sequential()方法将并行Stream转换为顺序Stream。并行Stream使用ForkJoinPool框架实现并行计算。但并非所有操作都适合并行化,需要考虑数据大小、操作特性、合并开销等因素。

Stream API与传统循环相比有多个优势。声明式风格使代码更简洁、意图更清晰。内部迭代由库控制,可以优化执行策略。易于并行化,只需调用parallel()方法。但Stream不是万能的,对于简单操作,传统循环可能更直接;对于有副作用的操作,需要谨慎使用。

其他新特性

Java 8还引入了多个其他重要特性。默认方法允许接口包含具体实现,解决了接口演化问题。接口可以定义default方法,实现类可以继承或重写。静态方法允许接口包含静态工具方法。

Optional类包装可能为null的值,强制开发者显式处理null情况。创建Optional对象使用of()、ofNullable()、empty()方法。访问值使用get()、orElse()、orElseGet()、orElseThrow()方法。转换值使用map()、flatMap()方法。过滤值使用filter()方法。Optional鼓励更安全的编程风格,减少NullPointerException。

新的日期时间API解决了旧的Date/Calendar类的缺陷。LocalDate表示日期,LocalTime表示时间,LocalDateTime表示日期时间。Instant表示时间戳。Duration表示时间间隔,Period表示日期间隔。DateTimeFormatter格式化解析日期时间。新API不可变、线程安全、设计清晰。

总结与展望

多线程编程和网络编程是Java高级应用的核心技术。多线程编程充分利用现代硬件能力,但引入了并发控制的复杂性。网络编程实现了分布式系统通信,但需要考虑网络异常和安全问题。Java在这两个领域提供了丰富的API,从低级的Socket到高级的并发工具,满足了不同层次的需求。

Lambda表达式和Stream API代表了Java语言的现代化转型。它们引入了函数式编程思想,使Java代码更加简洁、表达力更强。这些特性不仅改变了Java的语法,也影响了Java程序的设计模式。掌握这些新特性对于编写现代Java代码至关重要。

Java语言仍在持续演进,每个新版本都引入改进和新特性。作为Java开发者,持续学习是必要的。但无论语言如何变化,扎实的基础知识始终是核心。理解多线程模型、网络协议、算法数据结构等基础知识,才能更好地应用新特性,构建健壮、高效的Java应用程序。

实际开发中,这些技术往往结合使用。一个典型的Web应用可能使用线程池处理并发请求,使用网络通信访问后端服务,使用Stream处理数据,使用Lambda表达式简化回调。理解每个技术的适用场景和限制,根据具体需求选择合适的技术组合,是高级Java开发者的重要能力。


多线程、网络编程和Java新特性构成了Java高级编程的主要内容。掌握这些技术,开发者可以构建并发高效、网络感知、代码简洁的现代Java应用。Java生态系统仍在快速发展,新的框架、工具和最佳实践不断涌现,保持学习和实践是Java开发者持续成长的关键。