《Java并发编程实战》读书笔记0_第二章:线程安全性

本文章为读书笔记,字全是手敲的,整理书中知识点和自己的观点

要编写线程安全的代码其核心在于对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问。

“共享”意味着变量可以有多个线程同时访问,而“可变”则意味着变量的值在其生命周期内可以发生变化。

一个对象是否需要是线程安全的,取决于它是否被多个线程访问。这指的是在程序中访问对象的方式,而不是带向要是想的功能。

访问某个变量的代码越少,就越容易确保对变量的所有访问都实现正确的同步,同时也容易找出变量在哪些条件下被访问,Java语言并没有 强制要求将状态都封装在类中,开发人员完全可以将状态在某个公开的域(甚至公开的静态域)中,或者提供一个对内部对象的公开引用。然而,程序状态的封装性越好,就越容易实现程序的线程安全性,而且代码的维护人员也越容易保持这种方式。

有时候,面向对象中的抽象和封装会降低程序的性能(嗯...所以原来上学做ACM比赛的时候都用C......)


什么是线程安全性

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都表现出正确的行为,那么就称这个类是线程安全的。(所以在写代码的过程中需要不停重构代码的封装性)

在线程安全类中封装了必要的同步机制,因此客户端无需进一步采取同步措施。

无状态的对象一定是线程安全的(比如说Servlet,很多请求过来,Servlet可以实现分发)


原子性

书中举了一个例子 ++count; 这个操作就不是原子性,虽然说写的很紧凑,但是也是“读取 - 修改 - 写入”的复合操作。就容易出现某线程拿到旧的count值进行更新。

所以就有了“静态条件”,需要“先检查后执行”。没错,看着是不是有点像乐观锁的说......

扫描二维码关注公众号,回复: 5964044 查看本文章

为了解决符合操作的原子性问题,除了后面要说的加锁,jdk原生有一个很好的解决方案,就是java.util.concurrent.atomic包。但是这个只能实现这段代码中只有一个变量,如果有两个或多个变量的话,会破坏不变性的条件。


加锁

要保持状态的一致性,就需要在某个源自操作中更新所有相关状态的变量。所以对于复杂的情况,我们需要进行枷锁。

jdk提供了一种内置的锁机制来支撑原子性,关键字synchronized.但是这个也不能随便用,比如说上面提到的那种service分发,就极大的降低了性能。我貌似翻过这个错误...

重入:当某个线程拥有某个所得时候,还可以再次拿到这个锁,为什么会有这种现象呢?比如在继承的时候调用父类super的方法(带锁的),自己又重写了该方法(带锁的),就拿了两次。这时,JVM有个计数器为2。当所有对象的那个计数器都是0的时候,就说明这个锁没人用了,阻塞的线程就可以再次拿到锁了。

一种常见的加锁约定是,将所有的可变的状态都封装在对象内部,并通过对象的内置所对所有访问的可变状态的代码路径进行同步,使得在该对象上不会发生并发访问。

本章最后一句话,有个建议,非常合理:
当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O或者控制台I/O,一定不会持有锁)。

猜你喜欢

转载自www.cnblogs.com/pjjlt/p/pjjlt.html
今日推荐