并发编程-什么是线程安全?

定义“线程安全”这个概念是一个非常复杂的事情。越是正式而严肃的描述它越是复杂难懂,不仅没办法提供一些实际的指导,而且还没法有一个直观的理解。还有一些不太正式的描述,也看起来让人比较困惑。比如你通过某搜索引擎就会搜索到很多像下面这样的一些“定义”:

…可以被多个线程调用并且线程之间不会有错误的交互

(…can be called from multiple program threads without unwanted interactions between the threads.

或者:

可以被多个线程同时调用,并且在调用者的代码中没有任何其它的操作。

(…maybe called by more than one thread at a time without requiring any other action on the caller’s part)

当你看到这些定义的时候,毫无疑问你会犯晕。这些话就像你听到类似这样的话:“如果一个类可以被多个线程安全的访问那么这个类是安全的”。你咋一听觉得是没什么问题,逻辑上也没错,但,然并卵,你这不是废话吗,并没有对我们有实际的帮助。我如何区别线程安全的类和非线程安全的类呢?进一步说,“安全”(safe)的含义究竟是什么?

任何对线程安全性的定义中,最核心的概念就是正确性(correctness)。如果我们对于线程安全性的定义是模糊的(fuzzy),那是因为我们缺少对正确性的清晰的定义。所以接下来就来讨论正确性的问题。

正确性的含义是“一个类的行为遵循它的规范”。一个好的规范会定义约束对象状态不变的内容以及会定义能够描述该对象各种操作的后果。由于我们通常不能给我们的class写出明晰的规范,那么我们如何才能尽可能的知道我们的类是正确的呢?不能,但这并不能阻止我们使用这些类的步伐,只要我们说服了自己,“the code works”,代码是没有问题的。这种代码说服(code confidence)就很接近于我们前面说的“正确性”(correctness),所以让我们只是假定说单线程的正确性就是“所见即所得”(we know it when we see it)吧。现在我们已经给正确性做了一个比较清晰的定义了,不知道你有没有get到,那么是时候来定义一下什么是“线程安全”了:当多个线程访问某个类的时候,这个类依然能持续的表现出正确行为,那么我们认为这个类就是线程安全的。也就是确保正确性。

当多个线程访问某个类时,不管runtime使用什么样的调度方式或者这些线程怎么交替执行,在调用端的代码中也没有任何额外的同步机制以及其他协同机制,在这种情况下,这个类依然能表现正确,那么我们认为这个类是线程安全的。

由于任何一个单线程的program也可以看成是一个多线程program,所以如果这个program在单线程环境下都不能表现正常,那么这个program肯定不是线程安全的。如果一个对象被正确的实现,那么无论你是调用它的public方法还是读写public fields都不会违背它的任何不变性以及后置条件(post conditions)。一个线程安全的类的instance(实例)无论是在串行和并行的情况下都应该是坚挺的。(ps:就是说一个线程安全的类在任何情况下都应该是表现正常的。)

在线程安全的类中封装了有关同步的内容,所以客户端无需再采取同步措施

2.1.1.例子:无状态Servlet。

在第一章中,我们列举了一堆框架,这些框架创建很多线程,并在这些线程中调用你写的代码,,这就要求你写的代码必须是线程安全的。通常的话,线程安全的需求并不是让我们去直接使用线程,而是使用一些像Servlets框架的时候,会有线程安全的需求。接下来我们将会开发一个简单的例子,一个基于servlet的因数分解服务(factorisation service)并且一点点的扩展它,慢慢的加功能,同时确保这个service的线程安全性。

列表2.1 向我们展示了一个简单的因数分解的servlet。这个servlet从request中解包得到数值,然后进行因数分解,然后再把结果打包塞给servlet的response返回。

列表2.1 一枚无状态Servlet

StatelessFactorizer就像大多数的servlets一样,无状态,也就是stateless:什么样的类是stateless类呢?就是没有fields,没有引用其他类的fields的类。针对于指定运算的那些transient状态都只存在于local variables。相信你是知道的,这些local variables都只存在于线程的stack 里边(thread’s stack)。也就是说这些local variables只能被正在执行的线程内部访问,这些变量是不对外共享的。一个访问StatelessFactorizer的线程不会影响到其他正在访问StatelessFactorizer的线程的运算结果。因为这两个线程没有共享状态,也就是说她们访问的是不同的instances。由于访问一个stateless的对象的线程的各种操作并不会影响到其他的线程操作的正确性,所以只要是stateless对象都一定是线程安全的。

stateless对象一定是线程安全的

事实上大多数servlets都是无状态的,这就减轻了我们确保servlet线程安全的负担。但,如果servlet们想要在处理请求时保存一些信息,这个时候线程安全性便会变成一个问题。(ps:因为一旦有了fields就意味着可能存在线程安全问题。)

原文发布于微信公众号 - ImportSource(importsource)

原文发表时间:2016-07-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏吴伟祥

5个步骤,教你瞬间明白线程和线程安全

记得今年3月份刚来杭州面试的时候,有一家公司的技术总监问了我这样一个问题:你来说说有哪些线程安全的类?我心里一想,这我早都背好了,稀里哗啦说了一大堆。

1091
来自专栏java一日一条

使用 Python 编写多线程爬虫抓取百度贴吧邮箱与手机号

不知道大家过年都是怎么过的,反正栏主是在家睡了一天,醒来的时候登QQ发现有人找我要一份贴吧爬虫的源代码,想起之前练手的时候写过一个抓取百度贴吧发帖记录中的邮箱与...

2522
来自专栏一枝花算不算浪漫

[Redis]Redis 概述及基本使用规范.

5038
来自专栏owent

libcopp更新 (merge boost 1.59 context)

这个框架的上下文部分是使用了boost.context,但是从开始写libcopp到现在,boost.context也更新了几个版本。而之前几次merge基本都...

852
来自专栏coding for love

在线商城项目11-商品列表页的排序实现

请求后台接口会带上三种排序参数default,priceDown和priceUp。另外,如果不带参数,我们默认排序也是default。 这里,我们做一个简单的...

712
来自专栏阿杜的世界

Spring Boot:定制URL匹配规则

构建web应用程序时,并不是所有的URL请求都遵循默认的规则。有时,我们希望RESTful URL匹配的时候包含定界符“.”,这种情况在Spring中可以称之为...

1263
来自专栏小曾

.Net高级进阶,在复杂的业务逻辑下,如何以最简练的代码,最直观的编写事务代码?

本文将通过场景例子演示,来通俗易懂的讲解在复杂的业务逻辑下,如何以最简练的代码,最直观的编写事务代码。

892
来自专栏coding for love

在线商城项目08-数据库创建和商品集合的创建

因为six_tao中没有任何内容。我们需要为其创建一个集合或者插入文档,数据库才会显示。例如:

774
来自专栏测试驿栈

Jmeter(七)_if控制器+循环控制器+计数器控制接口分支

最近查阅了一下网上关于if控制器的文章,大同小异,几乎找不到原创,于是决定自己写一篇

1K2
来自专栏C/C++基础

Google C++编程风格指南(一)之头文件的相关规范

一个良好的编程规范和风格是一名程序猿成熟的标志。规范的编码可以减少代码冗余,降低出错概率,便于代码管理和代码交流等等,事实上,其作用远不止这些,我们要牢记编码规...

1461

扫码关注云+社区

领取腾讯云代金券