您的位置:首页 > 编程语言 > Java开发

[Java并发编程实战] 基础知识

2017-05-14 10:51 686 查看
什么是线程安全性?

定义线程安全性:当多个线程访问某个类时,这个类始终都能表现正确的行为,那么就称这个类是线程安全的。 单线程:所见及所知(we know it when we see it)

竞态条件

当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。

最常见的竞态条件类型就是“先检查后执行(Check-Then-Act)”操作,即通过一个可能失效的观测结果来决定下一步的动作。

例如,首先观测某个条件为真(文件X不存在),然后根据这个观察结果采用相应的动作(创建文件X),但事实上,在观测到这个结果以及开始创建文件文件之间,观测结果可能变得无效(另一个线程在这期间创建了文件X),从而导致各种问题(未预期的异常、数据被覆盖、文件被破坏等)。 如:单例模式的懒加载

复合操作

LazyInit递增运算 包含一组需要以原子方式执行(或者说不可分割)的操作。

要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或者之后读取和修改状态,而不是在修改状态的过程中。

原子操作是指,对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是一个以原子方式执行的操作。

在 java.util.concurrent.atomic 包中包含了一些原子变量类,用于实现在数值和对象引用上的原子状态转化。 通过用 AtomicLong 来代替 long 类型的计数器,能够确保所有对计数器状态的访问操作都是原子的。



要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

4.1 内置锁

Java 提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。

以关键字 synchrogazed 来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。静态的 synchrogazed 方法以 Class 对象作为锁。

synchronized (lock){
//访问或修改由锁保护的共享状态
}


每个 Java 对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock)

线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,而无论是通过正常的控制路径退出,还是通过从代码块中抛出异常退出。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。

Java 内置锁相当于一种互斥体(或互斥锁),这意味着最多只有一个线程能持有这种锁。 当线程 A 尝试获取一个由线程 B 持有的锁时,线程 A 必须等待或者阻塞,直到线程 B 释放这个锁。如果 B 永远不释放锁,那么 A 也将永远地等下去。 存在问题:性能低,并发性差。

4.2 重入

当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果某个线程视图获得一个已经由它自己持有的锁,那么这个请求就会成功。

“重入”意味着获取锁的操作的粒度是“线程”,而不是“调用”。重入的一种实现方式是:为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM 将记下锁的持有者,并且将获取计数值置为1。如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应地递减。当计数器为0时,这个锁将被释放。

https://www.zhihu.com/question/28113814

用锁来保护状态

对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁保护的。

每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。

对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。

活跃性与性能

如果对整个方法进行同步,这中简单且粗粒度的方法能确保线程安全性,但付出的代价却很高。不良并发

使用两个独立的同步代码块,每个同步代码块都只包含一小段代码

当执行时间较长的计算或者可能无法快速完成的操作(例如,网络I/O或控制台I/O),一定不要持有锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 线程安全