多线程程序,当一条原子指令刚执行一半的时候,CPU时间片耗尽,此时是立刻切换线程吗?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程程序,当一条原子指令刚执行一半的时候,CPU时间片耗尽,此时是立刻切换线程吗?相关的知识,希望对你有一定的参考价值。

我很好奇,当一个线程的一条原子指令执行一半的时候,恰好cpu时间片耗尽,这个时候,该条指令是放弃执行,CPU切换给其他线程执行,还是继续把该指令执行完再切换呢?

一条指令和原子操作是两个概念。
一条指令执行过程中不可以被中断,cpu只在一条指令执行完毕之后的中断周期查询中断,线程的切换就是由一个定时器实现的,比如定时器每隔200个时钟周期发起一次中断请求,cpu根据中断向量找到对应的中断处理程序,中断处理程序负责上下文切换。
原子操作是通过锁机制来保证。通过加锁,保证执行原子操作的时候,用到的共享内存不会被其它线程更改,也就是即使cpu切换到其它线程,其它线程也不能更改共享变量,只能等持有锁的线程释放锁。原子操作通常是通过锁来保护一段临界区的代码,这段代码包括多条指令,执行的过程中完全可以发生上下文切换。
锁的实现分为乐观锁和悲观锁。乐观锁就是通过一个空的循环操作实现,悲观锁会让拿不到锁的线程进入阻塞态,让出cpu。所以说对于单核cpu而言,乐观锁就是在浪费时间,因为只要它不让出cpu,那个拿到锁的线程就不会执行,它就拿不到锁,一直空转。但是多核cpu当中,拿到锁的线程可以在其它的内核当中执行,它空转几圈说不定锁就释放了,就少了一次上下文切换。
想学习这部分知识可以看《计算机组成原理》和《操作系统》。
参考技术A 首先,关于原子性的定义,是”不可中断的一个或一系列操作”。你这个问题涉及到临界点的情况。
在单处理器系统中,能够在单条指令中完成的操作都可以认为是” 原子操作”,因为中断只能发生于指令之间。 这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源互斥的原因。
在多处理器结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。
在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。 CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀”LOCK”,经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
总结来说,时间片到了,但是原子性操作未完成,还是会继续执行原子性操作。本回答被提问者和网友采纳
参考技术B 不叫放弃执行,应该叫暂停执行。转到其它线程,完了再回来继续执行追问

不过,【原子指令】的定义是不能中断,要么执行完毕,要么不执行。
但是我想,CPU时间片不可能分配的正好是【原子指令】的整数倍时间吧,如果恰好指令执行一半的时候,CPU时间片耗尽了,根据定义来说的话,因为不能中断,所以还得继续执行,等原子指令执行完毕后才切换给其他线程执行,是这样的吗?

追答

如果你要再缩小到最小级别的话,那就是你说的这种。 一条指令是不能分开的。

参考技术C 推测:从量的角度原子指令应该比时间片还小,一个时间片包含多个原子指令。切换动作比原子指令大多了,切换本身就是多条原子指令构成。

Java多线程之Atomic:原子变量与原子类

参考技术A

   一 何谓Atomic?

  Atomic一词跟原子有点关系 后者曾被人认为是最小物质的单位 计算机中的Atomic是指不能分割成若干部分的意思 如果一段代码被认为是Atomic 则表示这段代码在执行过程中 是不能被中断的 通常来说 原子指令由硬件提供 供软件来实现原子方法(某个线程进入该方法后 就不会被中断 直到其执行完成)

  在x 平台上 CPU提供了在指令执行期间对总线加锁的手段 CPU芯片上有一条引线#HLOCK pin 如果汇编语言的程序中在一条指令前面加上前缀 LOCK 经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低 持续到这条指令结束时放开 从而把总线锁住 这样同一总线上别的CPU就暂时不能通过总线访问内存了 保证了这条指令在多处理器环境中的原子性

   二 ncurrent中的原子变量

  无论是直接的还是间接的 几乎 ncurrent 包中的所有类都使用原子变量 而不使用同步 类似 ConcurrentLinkedQueue 的类也使用原子变量直接实现无等待算法 而类似 ConcurrentHashMap 的类使用 ReentrantLock 在需要时进行锁定 然后 ReentrantLock 使用原子变量来维护等待锁定的线程队列

  如果没有 JDK 中的 JVM 改进 将无法构造这些类 这些改进暴露了(向类库 而不是用户类)接口来访问硬件级的同步原语 然后 ncurrent 中的原子变量类和其他类向用户类公开这些功能

  ncurrent atomic的原子类

  这个包里面提供了一组原子类 其基本的特性就是在多线程环境下 当有多个线程同时执行这些类的实例包含的方法时 具有排他性 即当某个线程进入方法 执行其中的指令时 不会被其他线程打断 而别的线程就像自旋锁一样 一直等到该方法执行完成 才由JVM从等待队列中选择一个另一个线程进入 这只是一种逻辑上的理解 实际上是借助硬件的相关指令来实现的 不会阻塞线程(或者说只是在硬件级别上阻塞了) 其中的类可以分成 组

  AtomicBoolean AtomicInteger AtomicLong AtomicReference

  AtomicIntegerArray AtomicLongArray

  AtomicLongFieldUpdater AtomicIntegerFieldUpdater AtomicReferenceFieldUpdater

  AtomicMarkableReference AtomicStampedReference AtomicReferenceArray

  其中AtomicBoolean AtomicInteger AtomicLong AtomicReference是类似的

  首先AtomicBoolean AtomicInteger AtomicLong AtomicReference内部api是类似的 举个AtomicReference的例子

  使用AtomicReference创建线程安全的堆栈

  Java代码

  public class LinkedStack<T>

  private AtomicReference<Node<T》 stacks = new AtomicReference<Node<T》()

  public T push(T e)

  Node<T> oldNode newNode;

  while (true) //这里的处理非常的特别 也是必须如此的

  oldNode = stacks get()

  newNode = new Node<T>(e oldNode)

  if (pareAndSet(oldNode newNode))

  return e;

  

  

  

  public T pop()

  Node<T> oldNode newNode;

  while (true)

  oldNode = stacks get()

  newNode = oldNode next;

  if (pareAndSet(oldNode newNode))

  return oldNode object;

  

  

  

  private static final class Node<T>

  private T object;

  private Node<T> next;

  private Node(T object Node<T> next)

  this object = object;

  this next = next;

  

  

  

  然后关注字段的原子更新

  AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<T V>是基于反射的原子更新字段的值

  相应的API也是非常简

  单的 但是也是有一些约束的

  ( )字段必须是volatile类型的!volatile到底是个什么东西 请查看

  ( )字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致 也就是说调用者能够直接操作对象字段 那么就可以反射进行原子操作 但是对于父类的字段 子类是不能直接操作的 尽管子类可以访问父类的字段

  ( )只能是实例变量 不能是类变量 也就是说不能加static关键字

  ( )只能是可修改变量 不能使final变量 因为final的语义就是不可修改 实际上final的语义和volatile是有冲突的 这两个关键字不能同时存在

  ( )对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段 不能修改其包装类型(Integer/Long) 如果要修改包装类型就需要使用AtomicReferenceFieldUpdater

  在下面的例子中描述了操作的方法

  [java]

  import ncurrent atomic AtomicIntegerFieldUpdater;

  public class AtomicIntegerFieldUpdaterDemo

  class DemoData

  public volatile int value = ;

  volatile int value = ;

  protected volatile int value = ;

  private volatile int value = ;

  

  AtomicIntegerFieldUpdater<DemoData> getUpdater(String fieldName)

  return AtomicIntegerFieldUpdater newUpdater(DemoData class fieldName)

  

  void doit()

  DemoData data = new DemoData()

  System out println( ==> +getUpdater( value ) getAndSet(data ))

  System out println( ==> +getUpdater( value ) incrementAndGet(data))

  System out println( ==> +getUpdater( value ) decrementAndGet(data))

  System out println( true ==> +getUpdater( value ) pareAndSet(data ))

  

  public static void main(String[] args)

  AtomicIntegerFieldUpdaterDemo demo = new AtomicIntegerFieldUpdaterDemo()

  demo doit()

  

  

  在上面的例子中DemoData的字段value /value 对于AtomicIntegerFieldUpdaterDemo类是不可见的 因此通过反射是不能直接修改其值的

  AtomicMarkableReference类描述的一个<Object Boolean>的对 可以原子的修改Object或者Boolean的值 这种数据结构在一些缓存或者状态描述中比较有用 这种结构在单个或者同时修改Object/Boolean的时候能够有效的提高吞吐量

  AtomicStampedReference类维护带有整数 标志 的对象引用 可以用原子方式对其进行更新 对比AtomicMarkableReference类的<Object Boolean> AtomicStampedReference维护的是一种类似<Object int>的数据结构 其实就是对对象(引用)的一个并发计数 但是与AtomicInteger不同的是 此数据结构可以携带一个对象引用(Object) 并且能够对此对象和计数同时进行原子操作

  在本文结尾会提到 ABA问题 而AtomicMarkableReference/AtomicStampedReference在解决 ABA问题 上很有用

   三 Atomic类的作用

  使得让对单一数据的操作 实现了原子化

  使用Atomic类构建复杂的 无需阻塞的代码

  访问对 个或 个以上的atomic变量(或者对单个atomic变量进行 次或 次以上的操作)通常认为是需要同步的 以达到让这些操作能被作为一个原子单元

   无锁定且无等待算法

  基于 CAS (pare and swap)的并发算法称为 无锁定算法 因为线程不必再等待锁定(有时称为互斥或关键部分 这取决于线程平台的术语) 无论 CAS 操作成功还是失败 在任何一种情况中 它都在可预知的时间内完成 如果 CAS 失败 调用者可以重试 CAS 操作或采取其他适合的操作

  如果每个线程在其他线程任意延迟(或甚至失败)时都将持续进行操作 就可以说该算法是 无等待的 与此形成对比的是 无锁定算法要求仅 某个线程总是执行操作 (无等待的另一种定义是保证每个线程在其有限的步骤中正确计算自己的操作 而不管其他线程的操作 计时 交叉或速度 这一限制可以是系统中线程数的函数 例如 如果有 个线程 每个线程都执行一次CasCounter increment() 操作 最坏的情况下 每个线程将必须重试最多九次 才能完成增加 )

  再过去的 年里 人们已经对无等待且无锁定算法(也称为 无阻塞算法)进行了大量研究 许多人通用数据结构已经发现了无阻塞算法 无阻塞算法被广泛用于操作系统和 JVM 级别 进行诸如线程和进程调度等任务 虽然它们的实现比较复杂 但相对于基于锁定的备选算法 它们有许多优点 可以避免优先级倒置和死锁等危险 竞争比较便宜 协调发生在更细的粒度级别 允许更高程度的并行机制等等

   常见的

  非阻塞的计数器Counter

  非阻塞堆栈ConcurrentStack

lishixinzhi/Article/program/Java/gj/201311/27474

以上是关于多线程程序,当一条原子指令刚执行一半的时候,CPU时间片耗尽,此时是立刻切换线程吗?的主要内容,如果未能解决你的问题,请参考以下文章

Java高并发程序设计—— java内存模型和线程安全

荣耀黄金

原子操作

单片机中的原子操作是指啥?

原子性、可见性、有序性

简述CPU执行一条指令的过程