Android开发Android开发Android开发经验谈

线程方面的总结

2021-03-15  本文已影响0人  __Y_Q

1. sychronied 修饰普通方法和静态方法的区别, 什么是可见性

对象锁是用于对象实例方法, 或者一个对象实例上的, 类锁是用于类的静态方法或者一个类的 class 对象上的. 类的对象实例可以有多个, 但是每个类只有一个 class 对象, 所以不同对象实例的对象锁是互不干扰的, 但是每个类只有一个类锁. 但是类锁只是一个概念上的, 并不是真实存在的, 类锁其实是每个类对应的 class 对象. 类锁和对象锁之间是互不干扰的.

可见性是指当多个线程访问同一个变量时, 一个线程修改了这个变量的值, 其他线程能够立即看得到修改的值. 由于变成对变量的所有操作都必须在工作内存中进行, 而不能直接读写主内存中的变量, 那么对于共享变量首先是在自己的工作内存, 之后再同步到主内存. 可是并不会及时刷新到主内存中, 而是会存在一定的时间差, 所以这时候线程 A 对共享变量的操作对于线程 B 来说, 就不具备可见性了. 要解决可见性的问题 , 可以使用 volatile 关键字或者加锁.

详情见:从 Synchronized 到锁的优化
 

2. synchronized 的原理以及与 ReentrantLock 的区别.

synchronized 属于独占式悲观锁, 是通过 JVM 隐式实现的, synchronized 只允许同一时刻只有一个线程操作资源. 它涉及了两条指令 monitorenter / monitorexit, 每个对象都有一个监视器锁 monitor, monitor 被占用时就会处于锁定状态, 线程执行 monitorenter 指令时尝试获取 monitor 的所有权, 执行 monitorexit 时释放 monitor 对象. 当其他线程没有拿到 monitor 对象时, 则需要阻塞等待获取该对象.
而对于同步方法是依赖了方法修饰符 flags 上的 ACC_SYNCHRONIZED 实现的, JVM 就是根据该标识符来实现方法同步的. 当方法被调用时, 调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置, 如果设置了, 执行线程将先获取 monitor, 获取成功之后才能执行方法体. 执行完后再释放 monitor. 在方法执行期间, 其他任何线程无法再获得同一个 monitor 对象.

ReentrantLockLock 的默认实现方式之一, 它是基于 AQS 实现的. 通过内部的一个 state 字段来表示锁是否被占用. 0 表示未占用, 此时线程就可以通过 CAS 操作将 state 改为 1成功获取锁. 而其他线程只能排队等待获取资源.

两者相同点:

两者不同点

ReentrantLock的可以看 从 LockSupport 到 AQS 的简单学习中有源码分析
 

3. synchronized 做了哪些优化

JDK 1.6 引入了自旋锁, 适应性自旋锁, 锁消除, 锁粗化, 以及锁的升级等技术来减少锁操作的开销.

4. volatile 原理

通过使用 Lock 前缀的指令将当前处理器缓存行的数据写回到主内存, 将其他处理器的缓存无效. 需要数据操作的时候需要再次去主内存中读取.
通过插入内存屏障指令来禁止会影响变量可见性的指令重排序.指令如下

详情见:从 java 内存模型到 volatile 的简单理解
 

5. volatilesynchronize 有什么区别?

volatile 是最轻量的同步机制, 它保证了不同线程对这个变量进行操作时的可见性, 即一个线程修改了某个变量的值, 这个值对其他线程来说是立即可见的. 但是无法保证操作的原子性, 因此多线程下写的复合操作会导致线程安全问题.

synchronize 可以修饰方法或者以同步块的形式来进行使用, 确保了多个线程在同一时刻只能有一个线程处于方法或者同步块中, 保证了线程对变量访问的可见性和排他性, 又称为内置锁机制.
 

6. CAS 无锁编程的原理

线程处理器基本都支持 CAS 指令, 只不过实现的算法不同, 每一个 CAS 操作都分为三个运算符, 一个内存地址 V, 一个期望值 A 和一个新值 B. 操作的时候如果这个地址上存放的值等于期望值 A, 则将地址上的值更新为 B. 否则不做任何操作.

CAS 的基本思路就是: 如果这个地址上的值和期望值相等, 则给其赋予新值, 否则不做任何事. 但是要返回原值是多少. 循环 CAS 就是在一个循环里不断的做 CAS 操作, 直到成功为止.

使用 CAS 同时带来了三大问题

详情见:java 基础回顾 - 基于 CAS 实现原子操作的基本理解
 

7. AQS 原理

AQS 即抽象的队列同步器. 是用来构建锁或者其他同步组件的基础框架. 不能被实例化, 设计之初就是为了让子类通过继承 AQS 并实现它的抽象方法来管理同步状态. 如 ReentrantLock, ReentrantReadWriteLock, CountDownLatch 就是基于 AQS 实现的.

AQS 是基于 CLH 队列的变体实现的, 是一个双向同步队列. 它获取不到共享资源的线程封装成为一个 Node 节点加入到队列中, 每个 Node 节点维护一个 prevnext引用. 分别指向自己的前驱节点与后置节点. 通过 CAS, 自旋, 以及 LockSuppor.park() 等方式维护内部的一个使用 volatile 修饰的 int 类型共享变量 state 的状态, 使并发达到同步的效果.
详情见: 从 LockSupport 到 AQS 的简单学习
 

8. 线程的声明周期

Java 中线程的状态分为 6 种.

9. sleep, wait, yield 的区别, wait 的线程如何唤醒

sleep, yield 被调用后, 都不会释放当前线程所持有的锁.
调用 wait 方法会释放当前线程持有的锁, 而且被唤醒后会重新去竞争锁, 锁竞争到后才会执行 wait 方法后面的代码.
wait 通常被通用语线程间交互.
sleep 通常被用于暂停执行.
yield 方法使当前线程让出 CPU 执行权.
调用wait 方法后使用 notify / notifyAll 进行唤醒.
 

9. ThreadLocal 是什么

ThreadLocal 为每个线程都提供了变量的副本, 是的每个线程在某一时间访问到的并非同一对象, 这样就隔离了多个线程对数据的数据共享. 在内部实现上, 每个线程内部都有一个 ThreadLocalMap, 用来保存每个线程所拥有的变量副本.
详情可见: Android 消息机制之 ThreadLocal 深入源码分析 [ 二 ]
 

10. 为什么要使用线程池

合理的使用线程池能够带来下面三个好处.

线程池执行任务的流程

线程池中的几种拒绝策略为一下几种

上一篇 下一篇

猜你喜欢

热点阅读