Android 开发需要了解的线程和并发知识二

86 阅读3分钟

这是我参与8月更文挑战的第 4 天,活动详情查看:8月更文挑战

线程的状态

线程的第二篇我们先看看线程的状态,Java 中的线程状态分为 6 种,分别如下:

  • NEW:新建了一个线程,还没有调用 start 方法;
  • RUNNABLE:运行状态包括就绪 ready 和运行中 running 这两种。当一个线程调用了 start 方法后但是还没有获得 CPU 时间片时就处于就绪状态,当获取到 CPU 时间片开始运行时就是运行中状态;
  • BLOCKED:线程处于阻塞状态,比如进入了 synchronized 方法;
  • WAITING:线程无期限的等待另一个线程执行特定操作(通知或中断);
  • TIMED_WAITING:线程有时间限制的等待另一个线程,在时间到后会返回;
  • TERMINATED:线程执行完毕。
synchronized

下面我们继续看线程中的锁机制,当我们使用多线程对同一个数据进行操作的时候,由于 Java 内存模型中分为工作内存和主内存的概念,并且表面上的一条 i+1 语句在虚拟机中是由多条语句组成的。这样当多个线程重复对 i 进行操作的时候,假如说累加 10 万次,当每个线程进行累加的时候,可能 i 的值是一样的,并不会读取 i 的最新值,这样导致的结果就是累加的结果是小于 10 万的。这样就需要通过锁来解决了,对于加锁的代码,同一时间只能有一个线程访问。最方便的就是直接使用synchronized关键字修饰我们的方法,也可以通过synchronized来修饰一个对象,表示需要获取这个对象的锁才会执行其中的代码段。

synchronized是需要锁一个具体的对象的,synchronized方法中的锁就是当前的 Class 对象,这个对象的对象头中会存放锁的相关信息。

CAS

对于一些累加等比较单一的操作,我们并不需要使用锁机制,我们可以使用 CAS 的一些实现类,比如AtomicInteger。这种机制是使用了现代 CPU 支持的 compare and swap 的原子指令。在我们使用过程中,在某个地址上的数据,如果和我们期望的一样,就交换为新值。如果不能完成操作的话,会通过自旋再次尝试。因为如果不成功就发起,可能会触发线程切换,但是线程切换是比较消耗资源的,所以通过自旋在某些场景里会更加高效。

Volatile

前面提到过 Java 中分为主内存和工作内存,这样就会出现一个变量的值在主内存和工作内存是不一样的情况。volatile就是用来解决这个问题的,修饰变量后在进行写操作的时候会使用 CPU 提供的 Lock 指令,可以有两个能力,

第一:将当前 CPU 的的数据写回到系统内存

第二:写回操作时会使在其他 CPU 缓存中的数据无效。

这样就能够保证数据一致性问题。在某些情况下会使用 volatile 和 CAS 来替换 synchronized 来实现并发。

总结

上面介绍了线程的状态和几个常用的关键字,都是基于我学习的简单记录,如果需要深入研究还需阅读更多的资料。