1.首先,需要理解几个概念
1.同步(Synchronous):同步方法调用一旦开始,调用者必须等到前面的方法调用返回后,才能继续后续的行为,依次直到完成所有。
2.异步(Asynchronous):异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。异步方法通常会在另外一个线程中“真实”地执行。整个过程,不会阻碍当前调用者的工作。
3.并发(Concurrency):在一个时间段内,多件事情在这个时间段内交替执行。比如说:有两个队列,进入同一个入口,交替进入。
4.并行(Parallelism):多件事情在同一个时刻同事发生。比如说:有两个队列,分别进如两个入口。互不干扰。
注意:实际上,如果系统内只有一个CPU,而使用多进程或者多线程任务,那么真实环境中这些任务不可能是真实并行的,毕竟一个CPU一次只能执行一条指令,在这种情况下多进程或者多线程就是并发的,而不是并行的(操作系统会不停地切换多任务)。真实的并行也只可能出现在拥有多个CPU的系统中(比如多核CPU)。
5.临界区:临界区用来表示一种公共资源或者说共享数据,可以被多个线程使用,但是每一次只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源就必须等待。
6.阻塞(Blocking):一个线程占用了临界区资源,其他的线程就必须在这个临界区中等待,会导致线程挂起,这种情况就是阻塞。
7.非阻塞(Non-Blocking):没有一个线程可以妨碍其他线程执行,所有的线程都会尝试不断向前执行。
8.死锁(Deadlock):几个线程都不释放资源,也得不到自己所需要的资源,导致线程都没办法继续进行下去。
注意:可以通过jstack命令进行查看,其中所需要的pid可以通过命令行获取到,也可以通过jdk提供的jconsole.exe工具直接查看当前线程的pid。不懂自行百度
9.饥饿(Starvation):指某一个或者多个线程因为种种原因无法获得所要的资源,导致一直无法执行。比如它的优先级可能太低,而高优先级的线程不断抢占它需要的资源,导致低优先级线程无法工作
10.活锁(Livelock):比如两个线程都秉承着“谦让”的原则,主动将资源释放给他人使用,可能会导致资源不断地在两个线程间跳动,而没有一个线程可以同时拿到所有资源正常执行。这种情况就是活锁
总结:这里的概念学习起来比较枯燥,多进行理解,孰能生巧。
2.并发级别概念
根据控制并发的策略,我们可以把并发的级别分为阻塞、无饥饿、无障碍、无锁、无等待几种。
1.阻塞:一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。当我们使用synchronized关键字或者重入锁时,我们得到的就是阻塞的线程。
2.无饥饿:线程之间是有优先级的,那么线程调度的时候总是会倾向于先满足高优先级的线程。其中分为非公平锁与公平锁两种情况。公平锁不管新来的线程优先级多高,要想获得资源,就必须乖乖排队,这样所有的线程都有机会执行
3.无障碍:无障碍是一种最弱的非阻塞调度。两个线程如果无障碍地执行,那么不会因为临界区的问题导致一方被挂起。大家一起修改共享数据,把数据改坏了怎么办呢?对于无障碍的线程来说,一旦检测到这种情况,它就会立即对自己所做的修改进行回滚,确保数据安全。
4.无锁:无锁的并行都是无障碍的。在无锁的情况下,所有的线程都能尝试对临界区进行访问,但不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。
5.无等待:无锁只要求有一个线程可以在有限步内完成操作,而无等待则在无锁的基础上更进一步扩展。它要求所有线程都必须在有限步内完成,这样不会引起饥饿问题。如果限制这个步骤的上限,还可以进一步分解为有界无等待和线程数无关的无等待等几种,他们之间的区别只是对循环次数的限制不同
3.并行的两个重要定律
为什么要使用并行程序的问题前面已经进行了简单的探讨。总的来说,最重要的应该是处于两个目的。
第一,为了获得更好的性能;
第二,由于业务模型的需要,确实需要多个执行实体。
在这里,更加关注第一种情况,也就是有关性能的问题。将串行程序改造为并发程序,一般来说可以提高程序的整体性能,但是究竟能提高多少,甚至说究竟是否真的可以提高,还是一个需要研究的问题。目前,主要有两个定律对这个问题进行解答,一个是Amdahl定律,另外一个是Gustafson定律。
关于这两个定律,不是很理解,研究更深入客自行百度,总的来说,提升性能的方法:想办法提升系统并行的比例,同时增加CPU数量。
4.JMM(java内存模型)相关的一些概念
JMM关键技术点都是围绕着多线程的原子性、可见性、有序性来建立的
1.原子性:是指操作是不可分的,要么全部一起执行,要么不执行。在java中,其表现在对于共享变量的某些操作,是不可分的,必须连续的完成。比如a++,在操作a++的过程中,其他线程不会改变a的值。
注意:java中实现原子操作的方法大致有2种:锁机制、无锁CAS机制
2.可见性:可见性是指一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。比如:线程A对共享变量的修改要被线程B及时看到的话,需要进过以下步骤:1.线程A在自己的工作内存中修改变量之后,需要将变量的值刷新到主内存中 2.线程B要把主内存中变量的值更新到工作内存中
注意:可以使用volatile、synchronized、锁来实现
3.有序性:有序性指的是程序按照代码的先后顺序执行。为了性能优化,编译器和处理器会进行指令冲排序,有时候会改变程序语句的先后顺序
5.理解进程和线程
1.进程:我们经常使用windows系统,经常会看见.exe后缀的文件,双击这个.exe文件的时候,这个文件中的指令就会被系统加载,那么我们就能得到一个关于这个.exe程序的进程。
2.线程:线程是轻量级的进程,是程序执行的最小单元,使用多线程而不是多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程,线程的所有状态在java.lang.Thread中的State枚举中有定义:
New:表示刚刚创建的线程,这种线程还没有开始执行
RUNNABLE:运行状态,线程的start()方法调用后,线程会处于这种状态
BLOCKED:阻塞状态。当线程在执行的过程中遇到了synchronized同步块,但这个同步块被其他线程已获取还未释放时,当前线程将进入阻塞状态,会暂停执行,直到获取到锁。当线程获取到锁之后,又会进入到运行状态(RUNNABLE)
WAITING:等待状态。和TIMEWAITING都表示等待状态,区别是WAITING会进入一个无时间限制的等,而TIMEWAITING会进入一个有限的时间等待,那么等待的线程究竟在等什么呢?一般来说,WAITING的线程正式在等待一些特殊的事件,比如,通过wait()方法等待的线程在等待notify()方法,而通过join()方法等待的线程则会等待目标线程的终止。一旦等到期望的事件,线程就会再次进入RUNNABLE运行状态。
TERMINATED:表示结束状态,线程执行完毕之后进入结束状态。
注意:从NEW状态出发后,线程不能在回到NEW状态,同理,处理TERMINATED状态的线程也不能在回到RUNNABLE状态
个人理解:单个CPU一次只能运行一个任务,任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。其中,一个进程可以包括多个线程,一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。需要注意的是:一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存
注意:以上全部都是基本概念的理解,对于高并发的代码技巧掌握,不要急于求成,更多的是要着重于每一个基础知识点的逐步积累,万丈高楼平地起,一起加油骚年们。
原文地址:https://www.cnblogs.com/Roger2Lj/p/11562509.html