竞态条件
当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生静态条件。
举一个典型的例子,我们面试的时候经常会遇上这道题:两个线程交替打印0-100。
这道题的难点就在竞态条件上。当两个线程没有交替执行时就会出现类似1324这种情况。
临界区
当一段代码通过一些手段使得不能被多个线程同时访问,那么这段代码就被称为临界区。即,JVM中不同线程的程序计数器不会指向同一个临界区的内部。
由于临界区不会被不同线程同时访问所以临界区内部的代码是线程安全的。
锁
锁是用来保护临界区不被多个线程同时访问的工具。根据锁的不同性质,有以下几种对锁的分类。
-
内置锁 同步代码块,即我们平时最常用的使用
synchronized
实现的锁。这种锁机制通过JVM中的对象头来实现。任何对象都可以作为内置锁。 - 重入锁 又叫递归锁,当一个线程给临界区上锁后,不允许当前线程再次进入这个临界区,那么我们就说这个锁是不可重入的,反之就是可重入的。重入锁不因为递归造成死锁。
- 乐观锁,悲观锁 乐观锁,即当进入同步代码的时候,默认不会发生冲突,当竞态条件发生改变时会查看竞态条件是否已经被更新,如果被更新过则根据指定好的策略执行。悲观锁,当代码进入悲观锁代码块中的时候,默认会发生冲突,会立马锁住代码块。乐观锁一般采用CAS方法即(Compare And Swap)例如AtomicBoolean等。乐观锁本质上是一种无锁操作。
- 公平锁,非公平锁 公平锁,多个线程按时间顺序进入临界区。非公平锁,不安时间顺序(可以是优先级等)进入临界区。
- 排他锁,非排他锁 又叫独享锁(互斥锁)和共享锁,当一个上锁的临界区不允许其他任何线程进入时,这时就是排他锁,否则就是非排他锁。其典型代表是读写锁。其中,读锁是非排他的,写锁是排他的。
- 自旋锁 线程如果获取不到锁,不会自动阻塞,而是进入一个死循环,等待锁释放。优点是减少上下文切换,缺点是浪费CPU资源。
- 分段锁 将一个大的竞态条件分解为若干个小的竞态条件,不同竞态条件互不干扰。不同的临界区相互独立。典型的应用是ConcurrentHashMap;
-
偏向锁,轻量级锁,重量级锁 当一个锁经常被某个线程占用,这个锁会在不被任何线程占用时会称为偏向锁状态,此时当这个线程再次进入临界区时,需要的开销比较小。当自旋锁被某个线程持有后,自旋锁升级为轻量级锁,对于其他线程来说这个锁就是自旋锁。当自旋到一定次数后,再次升级到重量级锁,其他线程需要阻塞来等待所资源释放。
synchronized
就是在这三种锁之间切换。
Android多线程(一)
Android多线程(二)
Android多线程(四)
Android多线程(五)
Android多线程(六)
网友评论