安卓线程安全

安卓应用运行在jvm上,所以如对Java线程不熟的可以先看看Java线程安全

UI线程安全

  1. 耗时操作不在UI线程,否则会引发NAR,影响用户体验
  2. UI非线程安全,不在其他线程操作UI。
    既然像网络操作这种耗时的操作不能在主线程,而其他线程又不能更新UI。于是安卓提供了Handler和Looper。
    这里写图片描述
    可以看到一个线程只能有一个Looper和MessageQueue。既然是队列也就是先进先出的顺序结构,由Looper一个个取出。还可看成handler在外面,也就是一个线程可以有多个handler(很多几乎每个activity都建了对应主线程的handler)。

SharePerfences线程安全

getSharedPreferences和SharedPreferencesImpl都做了同步处理,所以SharePerfences是线程安全的。不过如果应用有多进程就要注意了,进程SharePerfences不安全。

变量访问线程安全

为啥说变量呢,因为常量的只读性天生线程安全。注意这里的常量指的是引用与内部的表示都不变。而我们平常说的String是常量就是说内部表示的不变性,而引用不变就是加个final。当然只要保证不对变量的引用及内部表示做更改,即使不写final也是线程安全的。当然这是非常不好的,因为这对代码理解及维护造成障碍。

关键字:volatiled、synchronized

volatiled:保证变量的可见性,同时基本类型都可用volatiled保证简单读写线程安全。 thinking in Java有写:“除了long和double类型,Java基本数据类型都是的简单读写都是原子的,而简单读写就是赋值和return语句。”因此而对于其他自加自减以及其他复合操作,是非原子操作。为啥long和double就不是原子的呢,因为他们占64位,在32位的操作系统或jvm中,long和double分高低位存储。不过其加了volatiled后,简单读写就是原子性的了。又因volatiled保证了可见性,所以简单读写就是线程安全了。

synchronized: 这是我们用的最多的线程安全的了,从字面意思就知道是同步。其同时保证原子性,可见性和有序性。它可用用的地方很多,但归结起来就两种:一种锁对象,一种锁类(Class对象)。所以其只同步对象,当一个线程执行这个对象同步方法时,其他线程无法执行这个对象的任何同步方法或代码。因为所有同步方法执行前都要获取对象的同步锁。

工具类:AtomicXXX、Striped64、Lock

AtomicXXX:比如AtomicBoolean常用语实现单例模式,AtomicInteger用于递增。还有AtomicReference为提供一般对象原子操作。都是通过volatiled和底层CAS实现。

Striped64:基类,提供比AtomicLong和AtomicDouble更加高效的实现。如LongAccumulator与LongAdder。
Lock:为了弥补synchronized带来的性能损耗,其可响应中断,且可以做到读写锁的分离。

集合类: BlockingQueue、ConcurrentXXX、CopyOnWriteXXX

BlockingQueue:提供了各种不同的实现,ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue等,用来实现线程安全的生产者-消费者队列。
ConcurrentXXX:常用集合的线程安全实现,并且为弱一致性的迭代器。不会被同一值返回两次,也不会抛出ConcurrentModificationException异常。
CopyOnWriteXXX:写时复制集合,迭代器还用的旧数组。但如果写操作很多拷贝影响效率。
还有Collections.synchronizedXXX方法也可提供安全集合,但要对迭代器进行同步处理。

其上都是java.util.concurrent包下的,线程池也在其下,包内还有很多像Semaphore、Phaser线程并发类。

数据库线程安全

安卓在使用数据库的时候常会产生这样的报错:
attempt to re-open an already-closed object
因为我们只使用一个数据库连接,Thread1和Thread2的都是由getDatabase()方法返回的相同连接。发生的什么事呢,在Thread2还在使用数据库连接时,Thread1可能已经把它给关闭了,那就是为什么你会得到崩溃异常。

android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
每次都创建了一个新的SQLiteOpenHelper,实际上你每次都创建了一个数据库的连接。如果你在同一时间用不同的数据库连接来对同一的数据库进行写操作的话,那么其中一个会失败。

解决:可以看到我们都是因为数据库连接的问题,而一个SQLiteOpenHelper就是一个连接,所以我们只要把SQLiteOpenHelper做成单例就行了。第一次使用时建立连接,在app退出时关闭连接。

文件线程安全

我们都知道文件操作是耗时操作,所以要另起线程。这时候就很可能有同时读写的情况,也就村在线程安全问题了。
解决:1.对文件操作单独写个类,进行读写时进行同步处理。如每个文件写个类怎会导致代码太多,总体用一个则会产生效率问题。文件读写顺序也具有不可预测性。
2. 用Executors.newSingleThreadExecutor进行读写操作,只用一个线程进行文件读写,不会有线程切换。并且具有队列性质,各个操作顺序先进先执行。

猜你喜欢

转载自blog.csdn.net/wanyouzhi/article/details/80923701