Moosphan / Android-Daily-Interview

:pushpin:每工作日更新一道 Android 面试题,小聚成河,大聚成江,共勉之~

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2019-07-29:如何保证线程安全?

MoJieBlog opened this issue · comments

2019-07-29:如何保证线程安全?

什么是线程安全?

其实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();
}

其实android里面线程安全还是相对较少的,记了那么多多线程的东西 都没怎么用过 这就很气

还是蛮多的。项目全局搜索了下,仅仅synchronized这一个关键字,我项目就出现了100多次,44个文件。这还不说其他方式。
项目中用到的synchronized关键字

其实android里面线程安全还是相对较少的,记了那么多多线程的东西 都没怎么用过 这就很气

还是蛮多的。项目全局搜索了下,仅仅synchronized这一个关键字,我项目就出现了100多次,44个文件。这还不说其他方式。
项目中用到的synchronized关键字

你是不是单利类写了这么多个

线程安全没有明确的定义,一般来讲,多线程情况下程序能表现正确(预期)的行为,那么就认为程序是线程安全的。看下《java并发编程实践这本书》会得到答案。

保证线程安全需要保证:
1.原子性:一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
2.可见性,一个线程对值做出更改,其他线程需要能看到这个值以及被改变
3.有序性,程序的执行要和代码的顺序一致。
对于线程安全,一般的做法是进行同步操作,在某一个时间点,只让一个线程来进行操作

commented

commented

谈谈个人想法:
1,线程安全的根本原因在于:进程间内存隔离,线程间内存共享;因此,在线程操作时其它线程可能会修改内存,从而导致模块执行的效果和预先设定不一致。

2,因此线程安全的保证,换句话说就是:保证当前内存在操作时,无法被其它线程执行

3,可采取的操作:锁同步、某些操作只允许在单一线程执行(例如主线程更新UI采取的就是这种策略)

其实android里面线程安全还是相对较少的,记了那么多多线程的东西 都没怎么用过 这就很气

还是蛮多的。项目全局搜索了下,仅仅synchronized这一个关键字,我项目就出现了100多次,44个文件。这还不说其他方式。
项目中用到的synchronized关键字

你是不是单利类写了这么多个

不仅仅是单例。好多场景。比如同一个页面有N个接口共同完成。再比如多线程List根据position移除item。

线程安全根本原因是:进程间内存隔离,线程间内存共享。如何保证主要有两个方面,

  • 线程间同步:线程间同步机制有很多种,主要通过来实现。具体可以看线程间如何同步
  • 线程间隔离:这个是不同于线程间同步机制,主要代表有ThreadLocal。具体可看ThreadLocal是如何保证线程安全及工作原理