一、必须知道的几个概念
1.同步和异步
同步和异步通常用来形容一次方法调用。
同步方法:调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
类比:商场买空调等带商家送货
异步方法:更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。异步方法会在另一个线程中“真实”地执行,整个过程不会阻碍调用者的工作。
类比:网购空调送货上门
2.并发和并行
都可以表示两个或者多个任务一起执行。
并发侧重于多个任何交替进行。
并行是真正意义上的同时执行。
3.临界区
临界区用来表示一种公共资源(或者说共享资源),可以被多个线程使用。但是每次只能有一次线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源就必须等待资源被释放。
在并发程序中,临界区资源是被保护的对象。
4.阻塞和非阻塞
阻塞和非阻塞通常用来形容多线程间的相互影响。
阻塞:一个线程占用了临界区资源,其他所有需要这个资源的线程必须在这个临界区中等待。等待会导致线程挂起,这种情况就是阻塞。如果占用资源的线程一直不愿意释放资源,那么其他所有阻塞在这个临界区上的线程都不能工作。
非阻塞:意思相反,它强调没有一个线程可以妨碍其他线程执行,所有线程都会尝试不断向前执行。
5.死锁、饥饿和活锁
死锁、饥饿和活锁都属于多线程的活跃性问题。如果发生上述情况,那么相关线程可能就不再活跃,也就是说它可能很难再继续往下执行了。
死锁:是最糟糕的一种情况。多个线程彼此之间相互占用了其他线程的资源,如果每个线程都不愿意释放自己占用的资源,那么这个状态将永远持续下去。
死锁未来不会解决。
饥饿:指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。1)该线程的优先级太低,高优先级的线程不断抢占它需要的资源导致无法工作。2)某一线程一直占着关键资源不放,导致其他需要这个资源的线程无法正常执行。
饥饿有可能在未来一段时间内解决。
活锁:两个线程智力不够,都秉承着谦让的原则,主动将资源释放给他人使用,最后导致资源不断在两个线程间跳动,而没有一个线程可以同时拿到所有资源正常执行。
二、并发级别
由于临界区的存在,多线程之间的并发无需受到控制。根据控制并发的策略,我们可以把并发的级别分为阻塞、无饥饿、无障碍、无锁、无等待几种。
1.阻塞
一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。
当使用synchronized关键字或者重入锁时,我们得到的就是阻塞的线程。synchronized关键字和重入锁都试图在执行后续代码前得到临界区的锁,如果得不到,线程就会被挂起等待,直到占有了所需资源为止。
2.无饥饿
如果线程之间是有优先级的,那么线程调度的时候总会倾向于先满足高优先级的线程。
非公平锁:允许高优先级的线程插队,这样可能导致低优先级的线程产生饥饿。
公平锁:所有线程安装先来后到的顺序执行,饥饿不会产生,所有线程有机会获得资源并执行。
3.无障碍
4.无锁
5.无等待
三、回到Java:JMM(Java内存模型)
前面介绍的并行程序的概念与语言无关。
并发程序比串行程序复杂很多,一个重要原因是并发程序中数据访问的一致性和安全性将会受到严重挑战。如何保证一个线程可以看到正确的数据是核心问题。
对于串行程序来说比较简单,该读到多少就是多少。、
对于并发程序来说,如果不加以控制,即使原本是1的数值也有可能读到2。
深入了解并行机制的前提下,定义一种规则保证多个线程间可以有效地、正确性协同工作——JMM为此而生。
JMM的关键点:多线程的原子性、可见性和有序性。
1.原子性
原子性是指一个操作是不可中断,即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
例子:32位系统中long型数据的读和写都不是原子性的,多线程之间会相互干扰
2.可见性
可见性是指当一个线程修改了某一个共享变量的值时,其他线程是否能够立即知道这个修改。
原因:缓存优化或者硬件优化、指令重排、编辑器的优化
3.有序性
在并发时,程序的执行可能会出现乱序。
原因:程序在执行代码的时候,可能会进程指令重排,重排后的指令与原指令的顺序未必一致。
4.哪些指令不能重排:Happen-Before原则
Java虚拟机和执行系统会对指令进行一定重排,但是指令重排是有一定原则的。
以下基本原则是指令重排不可违背的:
原文:https://www.cnblogs.com/zwhu1216/p/11387697.html