线程安全、synchronized和volatile关键字

一、什么是线程安全?

多线程下并发同时对共享数据进行读写,会造成数据混乱 = 线程不安全

当多线程并发访问临界资源时,如果破坏其原子性、可见性、有序性,可能会造成数据不一致。

  • 临界资源:共享资源(同一对象)同时读写,一次仅允许一个线程使用,才可保证其正确性。

1.1 synchronized

synchronized中文意思是同步,也称之为“同步锁”。

synchronized的作用是保证在同一时刻,被修饰的代码块或方法只会有一个线程执行,已达到保证并发安全的效果。

synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。

synchronized可以保证原子性、可见性、有序性。

1.2 原子性

原子性操作指相应的操作是单一不可分割的操作要想在多线程环境下保证原子性,则可以通过锁、synchronized来确保。volatile是无法保证复合操作的原子性

1.3 可见性

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

对于可见性,Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。另外,通过synchronizedLock也能够保证可见性,synchronizedLock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

1.4 有序性(指令重排)

有序性最终表述的现象是CPU是否按照既定代码顺序执行依次执行指令。

单线程的情况下只要保证最终执行结果正确即可as-if-serial

指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。也就是说,要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronizedLock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

二、如何解决线程不安全

2.1 破坏临界资源

将一些共享数据设置为只读,没有写的操作,线程肯定是安全的。

2.1.2 只读

final

2.2 局部变量

将一些共享资源变成局部变量,实现线程间的隔离,每个线程的局部变量会存在栈帧中,会在每个线程的栈帧内存中被创建多份,因此不存在共享。

2.3 ThreadLocal

将共享数据在每个工作内存中各拷贝一份,达到线程之间的一个数据隔离。

2.4.1 ThreadLocal是什么?

ThreadLocal也就是线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。线程的变量副本,每个线程隔离。

ThreadLocal是整个线程的全局变量,不是整个程序的全局变量。

2.4 volatile

volatile关键字具备两个特性,一是可见性,一是有序性(禁止指令重排)。

可见性:当一个变量被声明为volatile时,它会告诉编译器和CPU将该变量存储在主内存中,而不是线程的本地内存中。即每个线程读取的都是主内存中最新的值,避免了多线程并发下的数据不一致问题。

有序性:重排序可以分为编译器重排序和处理器重排序,volatile保证有序性,就是通过分别限制这两种类型的重排序。

2.5 原子类

通过原子类来解决数值的一些累加、累减、判断、计算以及赋值等等一系列的操作。

2.6 synchronized

基本可以用来处理线程不安全的各种场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

进击的女IT

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

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

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

打赏作者

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

抵扣说明:

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

余额充值