手记

Java并发编程:什么是线程安全性--基础知识

📖前言

来自我对一位我所崇拜的大佬文章的评论:

我:“喝罢黄河之水天上来,酒醒杨柳残月且偷欢,唱罢笑傲江湖祭沧海,雁渡寒潭有几只回还”

大佬:“年少正恰,纵码飞骋,略江山华月,有几人随傅虎踞龙盘。”


🚀进入正题

线程或者锁在并发编程中的作用,类似于铆钉和工字梁在土木工程中的作用。要建筑一座坚固的桥梁,必须正确地使用大量的铆钉和工字梁。同理,在构建稳健的并发程序时,必须正确地使用线程和锁。但这些终归只是一些机制。要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享(Shared)和可变的(Mutable)状态的访问。

  • 对象的状态:指存储在状态变量中的数据,当然也可能包含其他依赖对象的域。

    • 例如,某个 HashMap 的状态不仅存储在 HashMap 对象本身,还存储在许多 Map.Entry 对象中。在对象的状态中包含了任何可能影响其外部可见行为的数据。

  • 共享Shared:共享意味着变量可以有多个线程同时访问。

  • 可变Mutable:意味着变量的值在其生命周期内可以发生变化。

注释:讨论线程安全性,更应该侧重于如何防止在数据上发生不受控的并发访问。一个对象是否需要是线程安全的,取决于他是否被多个线程访问。(指的是在程序中访问对象的方式,而不是对象要实现的功能)

当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变量的访问。

  • 关键字 synchronized:提供独占的加锁方式。

  • voatile 类型变量:同步应该要包括的还有,显式锁(Explicit Lock)以及原子变量。

如果忽略了某一个同步的机制,可能会造成的后果,当多个线程访问同一个可变的状态变量时没有使用合适的同步,程序就会出错,修复:

  • 不在线程之间共享该状态变量

  • 将状态变量修改为不可变的变量

  • 在访问状态变量时使用同步

注释:当设计线程安全的类时,良好的面向对象技术、不可修改性,以及明晰的不变形规范都能起到一定的帮助作用。

在编写并发应用程序时,一种正确的编程方法就是:首先代码正确运行,然后再提高代码速度。即便如此,最好也只是当性能测试结果和应用需求告诉你必须提高性能,以及测量结果表明这种优化在实际环境中确实能带来性能提升时,才进行优化。


💕 什么是线程安全性

我们都知道:定义越正式,就越复杂,不仅很难提供由实际意义的指导建议,而且也很难从直观上去理解。网上的“定义”有很多,比如:

  • ······ 可以在多个线程中调用,并且在线程之间不会出现错误的交互。

  • ······ 可以同时被多个线程调用,而调用者无须执行额外的动作。

也难怪我们会对线程安全性感到困惑,因为听起来就想“如果某个类可以在多个线程中安全的使用,那么它就是一个线程安全的类。”虽然不存在很多争议,但是有什么实际的意义和帮助么。

“安全”的含义是什么:

  • 在线程安全的定义中,最核心的概念就是正确性。

  • 正确的含义是——某个类的行为与其规范完全一致

我将单线程的正确性近似定义为“所见即所知”,在对“正确性”给出了一个较为清晰的定以后,就可以定义线程安全性:

  • 当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。

  • 当多个线程访问某个类时,不关于刑事环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称呼这个类是线程安全的。

  • 由于单线程程序也可以看成多线程程序,若某个类在单线程环境下都不是正确的,那么它肯定不会是线程安全(也就是说一个程序首先要能正常的工作保证正确性)。


✨简单的示例

通常,线程安全性的需求并非来源于对县城的直接使用,而是使用像 Servlet 这样的框架(还有很多)。

一个基于 Servlet 的因数分解服务,并逐步扩展功能,并确保它的线程安全性。

一个无状态的 Servlet

@ThreadSafe
public class StatelessFactorizer implements Servlet {
	public void service(ServletRequest req,ServletResponse resp) {
		BigInteger i = extractFromRequest(req);
		BigInteger[] factors = factor(i);
        encodeIntoResponse(resp,factors);
	}
}

与大多数 Servlet 相同,StatelessFactorizer 是无状态的,它既不包含任何域,也不包含任何对其他类中域的引用。计算过程中的临时状态仅存在于线程栈上的局部变量中,并且只能由正在执行的线程访问。访问 StatelessFactorizer 的线程不会影响另外一个访问同一个 StatelessFactorizer 的线程的计算结果 ,因为这两个线程并没有共享状态,就好像他们都在访问不同的实例。由于线程访问无状态对象的行为并不会影响其他线程中操作的正确性,因此无状态对象是线程安全的。

  • 无状态对象一定是线程安全的。

  • 大多数 Servlet 都是无状态的,从而极大地降低了在实现 Servlet 线程安全性时的复杂性。只有当 Servlet 在处理请求时需要保存一些信息线程安全性才会成为一个问题。


作者:Sunny_Chen


0人推荐
随时随地看视频
慕课网APP