线程安全

也许你会惊讶,并发编程并不会涉及过多的线程或锁,不会多于建筑工程中使用的铆钉和I型梁。当然,要让桥梁坚固耐,需要正确使用大最的铆钉和I型梁:同样的道理, 构建并发程序也要正确使用线程和锁。然而这仅仅是纸上谈兵,获得最终结果的方式。编写线程安全的代码,本质上就是管理对状态的访问,而且通常都是共享的可变的状态。

通俗地说,一个对象的状态就是它的数据,储在状态变量中,比如实例域或静态域。对象的状态还包括了其他附厲对象的域。例如,HashMap的状态一部分存储到对象本身中,但同时也存储到很多Map.Entry对象中。一个对象的状态包含了任何会对它外部可见行为产生影响的数据。

所谓共享,是指一个变量可以被多个线程访问;所谓可变,是指变量的值在其生命周期内可以改变。我们讨论的线程安全性好像是关与代码的,但是我们真正要做的,是在不可控制的并发访问中保护数据。

一个对象是否应该是线程安全的取决于它是否会被多个线程访问。线程安全的这个性质,取决于程序中如何使用对象,而不是对象完成了什么。保证对象的线程安全性需要使用同步来协调对其可变状态的访问;若是做不到这一点,就会导致脏数据和其他不可预期的后果。

无论何时,只要有多于一个的线程访问给定的状态变量,而且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问。Java中首要的同步机制是synchronized关键字,它提供了独占锁。除此之外,术语“同步”还包括volatile变量,显示锁原子变量的使用。

你会想到在一些“特殊”情况下上述规则并不适用,不过你应该抵制住这种想法的诱惑。程序如果忽略了必要的同步,可能看上去可以运行,而且能够通过测试,甚至能正常地运行数年,但它仍然是存在隐患的,任何时刻都有可能崩溃。

在没有正确同步的情况下,如果多个线程访问同一个变量,你的程序就存在隐患。有3种方法修复它。 

  • 不要跨线程共享变量
  • 使状态变量变为不可变的
  • 在任何访问状态变量的时候使用同步

 

如果你没有在类的设计中考虑并发访问的因素,需要使用上面的3种方法对类的设计作重大的修改,所以修复程序的问题并不像听上去那样轻而易举。一开始就将一个类设计成是线程安全的,比在后期重新修复它更容易。 

在一个大型的程序中,识別出是否有多个线程可能访问给定的变量并不是一件容易的事情。幸运的是,面向对象技术,比如封装和数据隐藏,不仅帮助你编写组织良好的、可维护的类,同样的技术还可以帮助你创建线程安全的类。访问特定变量的代码越少,越容易确保使用恰当的同步,也越容易推断访问一个变童所需的条件。Java语言不强迫你封装所有的域,允许你将状态存储到公共域(甚至是公共静态域),或者将它的引用发布到其他的内部对象中,对程序的状态封装得越好,你的程序就越容易实现线程安全,同时有助于维护者保持这种线程安全性。

设计线程安全的类时,优秀的面向对象技术封装不可变性以及明确的不变约束——会给你提供诸多的帮助.

很多时候,良好的面向对象设计技术与现实世界需求不匹配:这种情况下,出于对系统性能和遗留代码的向后兼容性的考虑,良好的设计规则必须向现实世界作出妥协。有时候,抽象和封装会与性能产生冲突,虽然不像很多开发者认为的那样频繁,但是,首先让你的代码正确,然后再让它跑得快,总是一个良好的实践。即便如此,性能优化也只发生在特定的条件下:性能标准和需求要求你必须去做,或者依据相同的衡量标准,你的优化运行在真实环境中时发生了变化。如果你决定必须打破封装也无所谓。你的程序仍然可以是线程安全的,但是实现起来更困难些。而且,程序的线程安全性会变得更加脆弱,这增加了开发与维护的开销和风险。 

到目前为止,我们几乎可以互换地使用着术语“线程安全类”和“线程安全程序”。 一个线程安全程序是完全由线程安全类构成的么?不必要——完全由线程安全类构成的程序未必是线程安全的,线程安全程序也可以包含非线程安全的类。无论如何,只有当类封装了自己的状态时,“线程安全类”的概念才有意义。“线程安全性”可能成为用于约束代码的条款,或成为状态的条款,并且只能用于封装了自身状态的代码的整体,这个整体可能是一个对象,或是一个完整的程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值