JAVA高并发实战——走入并行世界

一、几个基本概念

   1、同步(Synchronous)和异步(Asynchronous)

  • 同步和异步通常用来形容一次方法调用
  • 同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后继的行为
  • 异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后继的操作。异步方法不会阻碍调用者的工作
    JAVA高并发实战——走入并行世界_第1张图片

   2、并发(Concurrency)和并行(Parallelism)

  • 它们都可以表示两个或者多个任务一起执行,但侧重点不同
  • 并发偏重于多个任务交替执行,多个任务之间有可能还是串行的
  • 并发是真正意义上的“同时执行”
    JAVA高并发实战——走入并行世界_第2张图片

   3、临界区

  • 临界区用来表示一种公共资源或者说共享数据,可以被多个线程使用
  • 每一次,只能有一个线程使用它,其他线程想要使用这个资源就必须等待

   4、阻塞(Blocking)和非阻塞(Non-Blocking)

  • 阻塞和非阻塞通常用来形容多线程间的相互影响
  • 一个线程占用了临界区的资源,那么其他所有需要这个资源的线程就必须在这个临界区等待。等待会导致线程挂起,这就是阻塞
  • 非阻塞强调没有一个线程可以妨碍其他线程执行,所有的线程都会尝试不断前行

   5、死锁、饥饿和活锁

  • 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
    JAVA高并发实战——走入并行世界_第3张图片

  • 饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行比如可能是它的线程优先级太低,而高优先级的线程不断抢占它需要的资源,导致低优先级线程无法工作饥饿可能在未来一段时间内解决(比如,高优先级的线程已经完成任务,不再执行)

  • 活锁是线程都秉承“谦让”的原则,主动将资源释放给他人使用,那么会导致资源不断在两个 线程之间跳动,而没有一个线程可以拿到所有资源正常执行

二、并发级别

由于临界区的存在,多线程之间的并发必须受到控制。根据并发的策略,我们可以把并发的级别分为阻塞,无饥饿,无障碍,无锁,无等待几种。

  • 阻塞:一个线程是阻塞的,那么在其他线程释放资源之言,当前线程无法继续执行

  • 无饥饿:如果线程之间是有优先级的,那么线程调度的时候总是会倾向于先满足高优先级的线程。也就是资源分配是不公平的。对于非公平锁来说,系统允许高优先级的线程插队,这样会导致低优先级线程产生饥饿。公平锁则按先来后到规则,不会产生饥饿。
    JAVA高并发实战——走入并行世界_第4张图片

  • 无障碍:无障碍是一种最弱的非阻塞调度。两个线程如果无障碍地执行,那么不会因为临界区的问题导致一方被挂起。对于无障碍线程来说,一旦检测到共享数据被修改坏了,就会立即对自己所做的修改进行回滚,确保数据安全。但如果没有数据竞争发生,就顺利完成工作,走出临界区。

  • 无锁:无锁的并行都是无障碍的。在无锁的情况下,所有的线程都能尝试对临界区进行访问,但不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。

  • 无等待:无等待要求在无锁的基础上更进一步扩展,它要求所有的线程都必须在有限步内完成

三、回到Java:JMM

   1、原子性(Atomicity)

  • 原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
  • 比如,对于一个静态全局变量int i,两个线程同时对它赋值,线程A给它赋值1,B给它赋值-1。那么不管两个线程以何种方式、何种步调工作。i要么是1,要么是-1.线程A和线程B之间没有干扰。这就是原子性的一个特点,不可被中断。

   2、可见性(Visibility)

  • 可见性是指当一个线程修改了某一个共享变量时,其他线程是否能够立即知道这个修改
  • 例如在CPU1和CPU2上各运行了一个线程,它们共享了变量t,由于编译器优化和硬件优化的缘故,在CPU1上的线程将变量t进行了优化,将其缓存在cache中或者寄存器中。在这种情况下,如果CPU2上的某个线程修改了变量t的实际值,那么CPU1上的线程可能也无法意识到这个改动。这就存在可见性问题。
    JAVA高并发实战——走入并行世界_第5张图片

   3、有序性(Ordering)

  • 在并发时,CPU会对指令执行顺序进行重新排序,以达到高性能。这就是指令重排
  • ==指令重排可以保证串行语义的一致性,但是没有义务保证多线程间的语义也一致
  • 哪些指令不能重排:Happen-Before原则
    1、程序顺序原则:一个线程内保证语义的串行性
    2、volatile规则:volatile变量的写先于读发生,这保证了volatile变量的可见性
    3、锁规则:解锁(unlock)必然发生在随后的加锁(lock)之前
    4、传递性:A先于B,B先于C,那么A必然先于C
    5、线程的start()方法先于它的每个动作
    6、线程的所有操作先于线程的终结(Thread.join())
    7、线程的中断(interrupt())先于被中断线程的代码
    8、对象的构造函数的执行,结束先于finalize()方法

你可能感兴趣的:(Java高并发)