1.线程安全性

1.线程安全性的基本概念

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

共享:可以由多个线程同时访问

可变:变量的值在其生命周期内可以发生变化

 

(2)java中主要同步机制有:关键字synchronized,volatile类型的变量,显式锁,原子变量

 

(3)如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序会报错,有三种方法修复

*1、不在线程之间共享该状态变量

*2、将状态变量修改为不可变的变量

*3、在访问状态变量时使用同步

 

2.什么是线程安全性

(1)线程安全的程序不一定全由线程安全类构成,完全由线程安全类构成的程序也不一定是线程安全的,而在线程安全类中也可以包含非线程安全的类。

 

(2)线程安全的类:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为。

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

 

(3)无状态对象一定是线程安全的

 

3.原子性

对于常见的一个场景:一个类中定义了一个count,每调用一次+1,统计调用次数的计数器

对于count,我们进行了“读取——修改——写入”的操作序列,并且其结果状态依赖于之前的状态

 

在并发编程中,上述情况是会出现问题的,这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,它有一个正式的名字:竞态条件

(1)竞态条件

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

最常见的竞态条件是“先检查后执行”:检查文件是否存在,不存在就创建。

 

(2)延迟初始化中的竞态条件

使用“先检查后执行”一种常见的情况是延迟初始化,延迟初始化的目的是将对象的初始化操作推迟到实际使用时才进行,同时要确保只被初始化一次。

懒汉式的单例模式,如果不用双重校验就会出现这个问题。

 

(3)复合操作

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

因此“先检查后执行”(延迟初始化)和“读取——修改——写入”(计数器)等操作必须是原子的

我们将“先检查后执行”(延迟初始化)和“读取——修改——写入”(计数器)等操作统称为复合操作:包含了一组必须以原子方式执行的操作以确保线程安全性。

 

4.加锁机制

(1)加锁机制

如果类中有一个状态变量,我们可以通过线程安全的对象(AtomicLong)来保证线程安全性。

 

但是当有多个状态变量的时候,是否添加更多的线程安全状态变量就够了?答案是:错!

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

 

(2)内置锁

java提供内置的锁机制支持原子性:同步代码块

同步代码块 = 作为锁的对象引用 + 作为由这个锁保护的代码块。

以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块。

*1)线程在进入同步代码块之前会自动获取锁,退出后会自动释放锁。获取内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。

*2)内置锁是互斥的

*3)由内置锁保护的同步代码块会以原子方式执行。

 

(3)重入

内置锁是可以重入的:当一个线程请求一个由其他线程持有的锁时,会阻塞,但是如果驶入获得一个由它自己持有的锁时,会成功。

重入,意味着获取锁的操作粒度是“线程”,而不是“调用”。

重入的实现方法:为每个锁关联一个计数值和所有者线程。计数值为0,表示没有线程持有。当线程请求未被持有的锁,JVM会记下锁的持有者,并将计数值置为1。如果该线程再次获取,将递增。线程退出同步代码块,计数值-1。当计数值为0,锁释放。

 

重入的意义在于:子类改写父类synchronized方法,然后调用父类方法,如果没有重入会死锁

 

(4)用锁来保护状态

*1)通过锁来构建一些协议以实现对共享状态的独占访问。只要遵循这些协议,就能确保状态的一致性。

访问共享状态的复合操作,必须原子操作避免竞态条件。同时,用锁来协调某个变量的访问时,访问变量的所有位置上都需要同一个锁

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

 

*2)当获取与对象关联的锁时,并不能阻止其他线程访问该对象,某个线程在获得对象的锁之后,只能阻止其他线程获得同一个锁

每个共享的和可变的变量都应该只由同一个锁来保护。

 

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

 

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

 

(5)活跃性与性能

只有对状态变量的操作加锁,其他能不加锁尽量不加锁。

当执行时间较长的计算或者可能无法快速完成的操作时(比如I/O),一定不要加锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹏哥哥啊Aaaa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值