2019-07-29:如何保证线程安全?
MoJieBlog opened this issue · 14 comments
什么是线程安全?
其实android里面线程安全还是相对较少的,记了那么多多线程的东西 都没怎么用过 这就很气
其实android里面线程安全还是相对较少的,记了那么多多线程的东西 都没怎么用过 这就很气
那你把你记得分享下吧,这也算是用处
有没有大佬来介绍介绍多线程里面的原子量的用法,还有原理
当多个线程要共享一个实例对象的值得时候,那么在考虑安全的多线程并发编程时就要保证下面3个要素:
原子性(Synchronized, Lock)
有序性(Volatile,Synchronized, Lock)
可见性(Volatile,Synchronized,Lock)
当然由于synchronized和Lock保证每个时刻只有一个线程执行同步代码,所以是线程安全的,也可以实现这一功能,但是由于线程是同步执行的,所以会影响效率。
下面是对3个要素的详细解释:
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在Java中,基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取共享变量时,它会去内存中读取新值。
普通的共享变量不能保证可见性,因为普通共享变量被修改后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
更新主存的步骤:当前线程将其他线程的工作内存中的缓存变量的缓存行设置为无效,然后当前线程将变量的值跟新到主存,更新成功后将其他线程的缓存行更新为新的主存地址
其他线程读取变量时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。
有序性:即程序执行的顺序按照代码的先后顺序执行。
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
可以通过volatile关键字来保证一定的“有序性”。
当在处理并发编程的时候,只要程序满足了原子性,可见性和有序性,那么程序就不会发生脏数据的问题。
如何保证线程安全?
- 线程安全的本质?
在多个线程访问共同资源时,在某一个线程对资源进行写操作中途(写入已经开始,还没结束),其他线程对这个写了一般资源进行了读操作,或者基于这个写了一半操作进行写操作,导致数据问题
所以:保证线程安全有以下操作
-
synchronized
保证方法内部或代码块内部资源互斥访问,同一时间,由同一Monitor监视代码,最终只能有一个线程在访问 -
volatile
保证加了 volatile 关键字的字段的操作具有同步性,以及对 long 和 double的操作原子性(long double 原子性这个简单说一下就行).因此 volatile 可以看做简单版本的 synchronized,但是volatile只能保证基本数据类型有效果,无法修改类似 User.name 或 ++ 原子性问题 -
java.util.concurrent.atomic 包:
下面有 AtomicInteger AtomicBoolean 等类,作用和 volatile 基本一致,可以看做通用版本的 volatile
AtomicInteger atomicInteger = new AtomicInteger(0);
...
atomicInteger.getAndIncrement();
- Lock / ReentrantReadWriteLock
加锁机制,但是比 synchronized 麻烦,不推荐直接使用,最好用更复杂的锁 ReadWriteLock
Lock lock = new ReentrantLock();
...
lock.lock(); try {
x++;
} finally {
lock.unlock();
}
线程安全没有明确的定义,一般来讲,多线程情况下程序能表现正确(预期)的行为,那么就认为程序是线程安全的。看下《java并发编程实践这本书》会得到答案。
保证线程安全需要保证:
1.原子性:一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
2.可见性,一个线程对值做出更改,其他线程需要能看到这个值以及被改变
3.有序性,程序的执行要和代码的顺序一致。
对于线程安全,一般的做法是进行同步操作,在某一个时间点,只让一个线程来进行操作
谈谈个人想法:
1,线程安全的根本原因在于:进程间内存隔离,线程间内存共享;因此,在线程操作时其它线程可能会修改内存,从而导致模块执行的效果和预先设定不一致。
2,因此线程安全的保证,换句话说就是:保证当前内存在操作时,无法被其它线程执行
3,可采取的操作:锁同步、某些操作只允许在单一线程执行(例如主线程更新UI采取的就是这种策略)
线程安全根本原因是:进程间内存隔离,线程间内存共享。如何保证主要有两个方面,
- 线程间同步:线程间同步机制有很多种,主要通过锁来实现。具体可以看线程间如何同步
- 线程间隔离:这个是不同于线程间同步机制,主要代表有ThreadLocal。具体可看ThreadLocal是如何保证线程安全及工作原理