一、什么是线程安全?
多线程下并发同时对共享数据进行读写,会造成数据混乱 = 线程不安全
当多线程并发访问临界资源时,如果破坏其原子性、可见性、有序性,可能会造成数据不一致。
- 临界资源:共享资源(同一对象)同时读写,一次仅允许一个线程使用,才可保证其正确性。
1.1 synchronized
synchronized中文意思是同步,也称之为“同步锁”。
synchronized的作用是保证在同一时刻,被修饰的代码块或方法只会有一个线程执行,已达到保证并发安全的效果。
synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。
synchronized可以保证原子性、可见性、有序性。
1.2 原子性
原子性操作指相应的操作是单一不可分割的操作。要想在多线程环境下保证原子性,则可以通过锁、synchronized来确保。volatile是无法保证复合操作的原子性。
1.3 可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
对于可见性,Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
1.4 有序性(指令重排)
有序性最终表述的现象是CPU是否按照既定代码顺序执行依次执行指令。
在单线程的情况下只要保证最终执行结果正确即可as-if-serial。
指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。也就是说,要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
二、如何解决线程不安全
2.1 破坏临界资源
2.2 只读
final
2.3 局部变量
每个线程的局部变量会存在栈帧中,会在每个线程的栈帧内存中被创建多份,因此不存在共享。
2.4 ThreadLocal
2.4.1 ThreadLocal是什么?
ThreadLocal也就是线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。
ThreadLocal是整个线程的全局变量,不是整个程序的全局变量。
2.5 volatile
volatile关键字具备两个特性,一是可见性,一是禁止指令重排。
可见性:当一个变量被声明为volatile时,它会告诉编译器和CPU将该变量存储在主内存中,而不是线程的本地内存中。即每个线程读取的都是主内存中最新的值,避免了多线程并发下的数据不一致问题。
有序性:重排序可以分为编译器重排序和处理器重排序,volatile保证有序性,就是通过分别限制这两种类型的重排序。
标签:volatile,变量,synchronized,线程,有序性,保证 From: https://blog.csdn.net/weixin_63908159/article/details/143020767