多线程是在同一个程序内部并行执行,因此会对相同的内存空间进行并发读写操作。
因此对于多线程代码,如果没有合适的预防措施,任何结果都是可能的。
Java 是最先支持多线程的开发的语言之一,Java 从一开始就支持了多线程能力,因此 Java 开发者能常遇到上面描述的问题场景。
操作系统为各个独立执行的进程分配各种资源,包括内存,文件句柄以及安全证书。如有需要线程之间甚至可以进行一些粗粒度的通信机制来交换数据,例如:套接字、信号处理器、共享内存、信号量以及文件等。
多线程出现的原因主要有3个:
资源利用率。某些情况下程序会等待某些外部操作,等待期间完全可以用于其他线程的执行,提高资源利用率。
公平性。不同用户与程序对计算机的资源有同等使用权,高效运行方式是提高粗粒度的时间分片(Time Slicing)让这些用户与程序共享计算机资源。
便利性。将复杂度任务拆解为多个子任务程序,每个程序在必要时互相通信,这样的实现会更加简化工作。
多线程的优势主要有4个:
充分发挥多处理器的计算能力。线程是最基本的调度单位,多线程有助于提高提高资源利用率,进而提升系统的吞吐率。
利于建模的简单性。化繁为简,我们现有的框架可以实现这个目标,例如:Servlet 和 RMI (Remote Method Invocation),框架解决细节问题,例如请求管理、线程创建、负债平衡,并在正确时刻分发给正确应用程序组件。
简化异步事件处理。我们现在的操作系统提供了一些高效的分发来实现多路IO,例如Unix 的select 和 poll等,Java 类库获得一族非阻塞的I/O包,这使得系统支持的线程数量得到极大提升。
更灵敏的响应界面。主要是GUI应用程序,例如:AWT 和 Swing 等,采用事件分发线程(Event Dispatch Thread,EDT)替代主事件循环。
Java 对多线程的支持是一把双刃剑,既简化了并发应用程序的开发,也提高对开发人员的技术要求。
多线程的风险主要体现在3个方面:
安全性问题:“永远不发生糟糕的事情”。多线程下会发生一种常见的并发安全问题,称为竞态条件(Race Condition),这时线程会由于无法预测的数据变化而出错,例如:多线程访问修改相同的变量会引入 非串行因素,这种问题极难定位。
@NotThreadSafe
public class UnsafeSequence {
private int value;
/** 返回唯一值**/
public int getNext(){
return value++;
}
}
@ThreadSafe
public class SafeSequence {
private int value;
/** 返回唯一值**/
public synchronized int getNext(){
return value++;
}
}
活跃性问题:“某件正确的事情最终会发生”。串行程序的活跃性问题之一是无意中造成的死循环;多线程程序的活跃性问题是:死锁、饥饿、活锁等。
性能问题:“我们通常希望正确的事情尽快发生”。性能问题包括多方面:服务时间过长,响应不灵敏,吞吐率过低,资源消耗过高或者可升缩性较低等。
几乎所有Java应用程序都是多线程的,当 JVM 启动时,它将为 JVM 的 内部任务(如:垃圾收集、终结操作等)创建后台线程,并创建一个主线程运行 main 方法。
常见的Java 多线程模块有4个:
Timer:定时器。
Servlet 和 JSP(JavaServer Page):Servlet 框架用于部署网页程序 和 分发来自HTTP 客户端请求。抵达服务器的请求会通过一个过滤器,然后分发到正确的 Servlet 或 JSP。每个 Servlet 是一个程序逻辑组件,高吞吐率的网站,多个客户端可能同时请求一个Servlet的服务。
因此, Servlet 需要满足被多线程访问的要求,它必须是线程安全的。
RMI(Remote Method Invocation):RMI 能够调用其他 JVM 的运行对象,通过 RMI 调用远程方法时,传递的参数会被打包(列集)到一个字节流,通过网络传输给远程 JVM,然后由远程 JVM 拆包(散集)并传递给远程方法。
当 RMI 代码调用远程对象,这个调用会被哪个线程执行?这其实是在由 RMI 管理的线程中进行调用的,因此,它必须是线程安全的。
Swing 和 AWT:GUI 应用程序的异步性。
本节主要关注Java 多线程,讲述了多线程的基础知识:什么是多线程,多程带来了哪些问题,多线程有哪些优点,以及JVM与Java框架对多线程的支持。对于线程模型,可以点击《一文带你读懂:系统线程模型与实现原理》进行了解。
—END—