一文读懂《Java并发编程实战》:第1章 多线程安全性与风险

点击上方蓝字关注我们!

多线程是在同一个程序内部并行执行,因此会对相同的内存空间进行并发读写操作。

  • 如果一个线程在读一个内存时,另一个线程正向该内存进行写操作,那进行读操作的那个线程将获得什么结果呢?是写操作之前旧的值?还是写操作成功之后的新值?或是一半新一半旧的值?

  • 如果是两个线程同时写同一个内存,在操作完成后将会是什么结果呢?是第一个线程写入的值?还是第二个线程写入的值?还是两个线程写入的一个混合值?

因此对于多线程代码,如果没有合适的预防措施,任何结果都是可能的。

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++;
  }
}

活跃性问题:“某件正确的事情最终会发生”。串行程序的活跃性问题之一是无意中造成的死循环;多线程程序的活跃性问题是:死锁、饥饿、活锁等。

性能问题:“我们通常希望正确的事情尽快发生”。性能问题包括多方面:服务时间过长,响应不灵敏,吞吐率过低,资源消耗过高或者可升缩性较低等。

JVM与Java框架对多线程的支持

几乎所有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—

推荐

一文带你读懂:系统线程模型与实现原理

一文读懂《Effective Java》第54条:谨慎的使用本地方法

一文读懂《Effective Java》第53条:接口优先于反射机制

一文读懂《Effective Java》第52条:通过接口引用对象

一文读懂《Effective Java》第48条:如果需要精确答案,请避免使用float和double

一文读懂《Effective Java》第42条:慎用可变参数

一文读懂《Effective Java》第41条:慎用重载

一文读懂《Effective Java》第23条:不要在新代码中使用原生态类型

一文读懂《Effective Java》第7条:避免使用终结方法

一文读懂《Effective Java》第3条:用私有构造器或者枚举类型强化Singleton属性

扫描二维码

获取技术干货

后台技术汇

点个“在看”表示朕

已阅

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值