并发编程——线程安全问题(三)

多线程会产生哪些安全问题?

1.共享数据被线程操作之后的数据混乱,经典案例《出售火车票》。微博爆出12306卖出同一个座位的两张票。

2.出现死锁现象。程序直接卡死,线程彼此之间等待对方释放锁资源。一双筷子被两个人各持有一根,都想得到对方的筷子。僵持不下。

3.读写操作不一致,数据正在写入还未刷新到数据库。被读,拿到了脏数据。单纯的只有读操作是不会发生的。

 

为什么会产生上述安全的问题?

都是java内存模型惹的祸啊。

什么是java内存模型呢?

共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

 

这里有两个线程对主内存的数据做减减操作,两个子线程会先将数据copy一份副本存放在本地内存。假设t1做了减减操作,此时本地内存的count = 9,主内存还是10。这个时候t1要将操作后的数据结果刷新到主内存,来告诉别的线程数据已经发生了改变。而问题就出现再这里,刷新到主内存是需要时间的。如果不及时就会出现数据混乱问题。也就是t2也是由10做的减减操作,这样就出现错误了。

错误原因

线程之间是不可见的,读取的是副本,没有及时读取到主内存结果。

如何解决这个问题呢?

1.使用Volatile 保证线程间的可见性

可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。

在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主存。

2.加锁 Synchronized

每次只能有一个线程进行操作。也就是一个茅坑只能蹲 一个人,谁蹲坑谁把门锁上。

疑问:啥是可见性

多线程是有三大特性的

原子性

可见性

有序性

什么是原子性

即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。

我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。

原子性其实就是保证数据一致、线程安全一部分,

什么可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。

什么是有序性

程序执行的顺序按照代码的先后顺序执行。

一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:

int a = 10;    //语句1

int r = 2;    //语句2

a = a + 3;    //语句3

r = a*a;     //语句4

则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值