1、加微信itsoku,发送:1024,获取 100G 高质量计算机学习视频!第1篇:必须知道的几个概念 同步(Synchronous)和异步(Asynchronous)同步和异步通常来形容一次方法调用,同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中“真实”地执行。整个过程,不会阻碍调用者的工作。如图:上图中显示了同步方法调用和异步方法调用的区别。对于调用者来说,异步调用似乎是一瞬间就完成的。如果异步调用需要返回结果,那么当这个异步调用真实完成时,则会
2、通知调用者。打个比方,比如购物,如果你去商场买空调,当你到了商场看重了一款空调,你就向售货员下单。售货员去仓库帮你调配物品。这天你热的是在不行了,就催着商家赶紧给你送货,于是你就在商店里面候着他们,直到商家把你和空调一起送回家,一次愉快的购物就结束了。这就是同步调用。不过,如果我们赶时髦,就坐在家里打开电脑,在电脑上订购了一台空调。当你完成网上支付的时候,对你来说购物过程已经结束了。虽然空调还没有送到家,但是你的任务已经完成了。商家接到你的订单后,就会加紧安平送货,当然这一切已经跟你无关了。你已经支付完成,想干什么就能去干什么,出去溜几圈都不成问题,等送货上门的时候,接到商家的电话,回家一趟签
3、收就完事了。这就是异步调用。并发(Concurrency)和并行(Parallelism)并发和并行是两个非常容易被混淆的概念。他们都可以表示两个或者多个任务一起执行,但是侧重点有所不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的,而并行是真正意义上的“同时执行”,下图很好地诠释了这点。公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!大家排队在一个咖啡机上接咖啡,交替执行,是并发;两台咖啡机上面接咖啡,是并行。从严格意义上来说,并行的多任务是真的同时执行,而对于并发来说,这个过程只是交替的,一会执行任务A,一会执行任务B,系统会不停
4、地在两者之间切换。但对于外部观察者来说,即使多个任务之间是串行并发的,也会造成多任务间并行执行的错觉。并发说的是在一个时间段内,多件事情在这个时间段内交替执行。并行说的是多件事情在同一个时刻同事发生。实际上,如果系统内只有一个CPU,而使用多进程或者多线程任务,那么真实环境中这些任务不可能是真实并行的,毕竟一个CPU一次只能执行一条指令,在这种情况下多进程或者多线程就是并发的,而不是并行的(操作系统会不停地切换多任务)。真实的并行也只可能出现在拥有多个CPU的系统中(比如多核CPU)。临界区 临界区用来表示一种公共资源或者说共享数据,可以被多个线程使用,但是每一次只能有一个线程使用它,一旦临界
5、区资源被占用,其他线程要想使用这个资源就必须等待。比如,一个办公室里有一台打印机,打印机一次只能执行一个任务。如果小王和小明同时需要打印文件,很明显,如果小王先发了打印任务,打印机就开始打印小王的文件,小明的任务就只能等待小王打印结束后才能打印,这里的打印机就是一个临界区的例子。在并行程序中,临界区资源是保护的对象,如果意外出现打印机同时执行两个任务的情况,那么最有可能的结果就是打印出来的文件是损坏的文件,它既不是小王想要的,也不是小明想要的。阻塞(Blocking)和非阻塞(Non-Blocking)公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频
6、!阻塞和非阻塞通常用来形容很多线程间的相互影响。比如一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中等待。等待会导致线程挂起,这种情况就是阻塞。此时,如果占用资源的线程一直不愿意释放资源,那么其他线程阻塞在这个临界区上的线程都不能工作。非阻塞的意思与之相反,它强调没有一个线程可以妨碍其他线程执行,所有的线程都会尝试不断向前执行。死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)死锁、饥饿和活锁都属于多线程的活跃性问题。如果发现上述几种情况,那么相关线程就不再活跃,也就是说它可能很难再继续往下执行了。死锁应该是最糟糕的一种情况了(当然,其
7、他几种情况也好不到哪里去),如下图显示了一个死锁的发生:A、B、C、D四辆小车都在这种情况下都无法继续行驶了。他们彼此之间相互占用了其他车辆的车道,如果大家都不愿意释放自己的车道,那么这个状况将永远持续下去,谁都不可能通过,死锁是一个很严重的并且应该避免和实时小心的问题,后面的文章中会做更详细的讨论。饥饿是指某一个或者多个线程因为种种原因无法获得所要的资源,导致一直无法执行。比如它的优先级可能太低,而高优先级的线程不断抢占它需要的资源,导致低优先级线程无法工作。在自然界中,母鸡给雏鸟喂食很容易出现这种情况:由于雏鸟很多,食物有限,雏鸟之间的事务竞争可能非常厉害,经常抢不到事务的雏鸟有可能被饿死
8、。线程的饥饿非常类似这种情况。此外,某一个线程一直占着关键资源不放,导致其他需要这个资源的线程无法正常执行,这种情况也是饥饿的一种。于死锁想必,饥饿还是有可能在未来一段时间内解决的(比如,高优先级的线程已经完成任务,不再疯狂执行)。活锁是一种非常有趣的情况。不知道大家是否遇到过这么一种场景,当你要做电梯下楼时,电梯到了,门开了,这是你正准备出去。但很不巧的是,门外一个人当着你的去路,他想进来。于是,你很礼貌地靠左走,礼让对方。同时,对方也非常礼貌的靠右走,希望礼让你。结果,你们俩就又撞上了。于是乎,你们都意识到了问题,希望尽快避让对方,你立即向右边走,同时,他立即向左边走。结果,又撞上了!不过
9、介于人类的智慧,我相信这个动作重复两三次后,你应该可以顺利解决这个问题。因为这个时候,大家都会本能地对视,进行交流,保证这种情况不再发生。但如果这种情况发生在两个线程之间可能就不那么幸运了。如果线程智力不够。且都秉承着“谦让”的原则,主动将资源释放给他人使用,那么久会导致资源不断地在两个线程间跳动,而没有一个线程可以同时拿到所有资源正常执行。这种情况就是活锁。死锁的例子 package com.jvm.visualvm;公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!/*Java干货铺子,只生产干货,公众号:javacode2018*/public
10、 class Demo4 public static void main(String args)Obj1 obj1=new Obj1();Obj2 obj2=new Obj2();Thread thread1=new Thread(new SynAddRunalbe(obj1,obj2,1,2,true);thread1.setName(thread1);thread1.start();Thread thread2=new Thread(new SynAddRunalbe(obj1,obj2,2,1,false);thread2.setName(thread2);thread2.start(
11、);/*线程死锁等待演示 */public static class SynAddRunalbe implements Runnable Obj1 obj1;Obj2 obj2;int a,b;boolean flag;public SynAddRunalbe(Obj1 obj1,Obj2 obj2,int a,int b,boolean flag)this.obj1=obj1;this.obj2=obj2;this.a=a;this.b=b;this.flag=flag;Override public void run()try if(flag)synchronized(obj1)Threa
12、d.sleep(100);synchronized(obj2)System.out.println(a+b);else synchronized(obj2)Thread.sleep(100);synchronized(obj1)System.out.println(a+b);catch(InterruptedException e)e.printStackTrace();公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!运行上面代码,可以通过jstack查看到死锁信息:thread1持有com.jvm.visualvm.Demo4的 锁,等?获?Ob
13、j2的锁thread2持有com.jvm.visualvm.Demo4的 锁,等?获?Obj1的锁,两个线程相互等待获取对方持有的锁,出现死锁。饥饿死锁的例子 public static class Obj1 public static class Obj2 thread2#13 prio=5 os_prio=0 tid=0 x0000000029225000 nid=0 x3c94 waiting for monitor entry 0 x0000000029c9f000 java.lang.Thread.State:BLOCKED(on object monitor)at com.jvm.
14、visualvm.Demo4$SynAddRunalbe.run(Demo4.java:50)-waiting to lock (a com.jvm.visualvm.Demo4$Obj1)-locked (a com.jvm.visualvm.Demo4$Obj2)at java.lang.Thread.run(Thread.java:745)Locked ownable synchronizers:-Nonethread1#12 prio=5 os_prio=0 tid=0 x0000000029224800 nid=0 x6874 waiting for monitor entry 0
15、x0000000029b9f000 java.lang.Thread.State:BLOCKED(on object monitor)at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:43)-waiting to lock (a com.jvm.visualvm.Demo4$Obj2)-locked (a com.jvm.visualvm.Demo4$Obj1)at java.lang.Thread.run(Thread.java:745)Locked ownable synchronizers:-Nonepackage com.jv
16、m.jconsole;import java.util.concurrent.*;/*Java干货铺子,只生产干货,公众号:javacode2018*/public class ExecutorLock private static ExecutorService single=Executors.newSingleThreadExecutor();public static class AnotherCallable implements Callable Override public String call()throws Exception System.out.println(in
17、AnotherCallable);return annother success;公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!执行代码,输出:使用jstack命令查看线程堆栈信息:public static class MyCallable implements Callable Override public String call()throws Exception System.out.println(in MyCallable);Future submit=single.submit(new AnotherCallable();retu
18、rn success:+submit.get();public static void main(String args)throws ExecutionException,InterruptedException MyCallable task=new MyCallable();Future submit=single.submit(task);System.out.println(submit.get();System.out.println(over);single.shutdown();in MyCallablepool-1-thread-1#12 prio=5 os_prio=0 t
19、id=0 x0000000028e3d000 nid=0 x58a4 waiting on condition 0 x00000000297ff000 java.lang.Thread.State:WAITING(parking)at sun.misc.Unsafe.park(Native Method)-parking to wait for (a java.util.concurrent.FutureTask)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)at java.util.concurrent
20、.FutureTask.awaitDone(FutureTask.java:429)at java.util.concurrent.FutureTask.get(FutureTask.java:191)at com.jvm.jconsole.ExecutorLock$MyCallable.call(ExecutorLock.java:25)at com.jvm.jconsole.ExecutorLock$MyCallable.call(ExecutorLock.java:20)at java.util.concurrent.FutureTask.run(FutureTask.java:266)
21、at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)at java.lang.Thread.run(Thread.java:745)Locked ownable synchronizers:-(a java.util.concurrent.ThreadPoolExecutor$Worker)main#1 prio=5 os
22、_prio=0 tid=0 x00000000033e4000 nid=0 x5f94 waiting on condition 0 x00000000031fe000 java.lang.Thread.State:WAITING(parking)at sun.misc.Unsafe.park(Native Method)-parking to wait for (a java.util.concurrent.FutureTask)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)at java.util.c
23、oncurrent.FutureTask.awaitDone(FutureTask.java:429)公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!堆栈信息结合图中的代码,可以看出主线程在32行处于等待中,线程池中的工作线程在25行处于等待中,等待获取结果。由于线程池是一个线程,AnotherCallable得不到执行,而被饿死,最终导致了程序死锁的现象。更多好文章 1.spring高手系列(正在连载中)2.Java高并发系列(共34篇)3.MySql高手系列(共27篇)4.Maven高手系列(共10篇)5.Mybatis系列(共12篇)6.聊聊
24、db和缓存一致性常见的实现方式7.接口幂等性这么重要,它是什么?怎么实现?8.泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!加微信itsoku,发送:1024,获取 100G 高质量计算机学习视频!at java.util.concurrent.FutureTask.get(FutureTask.java:191)at com.jvm.jconsole.ExecutorLock.main(ExecutorLock.java:32)Locked ownable synchronizers:-None公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学
25、习视频!第2篇:并发级别 由于临界区的存在,多线程之间的并发必须受到控制。根据控制并发的策略,我们可以把并发的级别分为阻塞、无饥饿、无障碍、无锁、无等待几种。阻塞 一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。当我们使用synchronized关键字或者重入锁时,我们得到的就是阻塞的线程。synchronize关键字和重入锁都试图在执行后续代码前,得到临界区的锁,如果得不到,线程就会被挂起等待,直到占有了所需资源为止。无饥饿(Starvation-Free)如果线程之间是有优先级的,那么线程调度的时候总是会倾向于先满足高优先级的线程。也就是说,对于同一个资源的分配,是不公
26、平的!图1.7中显示了非公平锁与公平锁两种情况(五角星表示高优先级线程)。对于非公平锁来说,系统允许高优先级的线程插队。这样有可能导致低优先级线程产生饥饿。但如果锁是公平的,按照先来后到的规则,那么饥饿就不会产生,不管新来的线程优先级多高,要想获得资源,就必须乖乖排队,这样所有的线程都有机会执行。无障碍(Obstruction-Free)无障碍是一种最弱的非阻塞调度。两个线程如果无障碍地执行,那么不会因为临界区的问题导致一方被挂起。换言之,大家都可以大摇大摆地进入临界区了。那么大家一起修改共享数据,把数据改坏了怎么办呢?对于无障碍的线程来说,一旦检测到这种情况,它就会立即对自己所做的修改进行回
27、滚,确保数据安全。但如果没有数据竞争发生,那么线程就可以顺利完成自己的工作,走出临界区。如果说阻塞的控制方式是悲观策略,也就是说,系统认为两个线程之间很有可能发生不幸的冲突,因此以保护共享数据为第一优先级,相对来说,非阻塞的调度就是一种乐观的策略。它认为多个线程之间很有可能不会发生冲突,或者说这种概率不大。因此大家都应该无障碍地执行,但是一旦检测到冲突,就应该进行回滚。公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!从这个策略中也可以看到,无障碍的多线程程序并不一定能顺畅运行。因为当临界区中存在严重的冲突时,所有的线程可能都会不断地回滚自己的操作,
28、而没有一个线程可以走出临界区。这种情况会影响系统的正常执行。所以,我们可能会非常希望在这一堆线程中,至少可以有一个线程能够在有限的时间内完成自己的操作,而退出临界区。至少这样可以保证系统不会在临界区中进行无限的等待。一种可行的无障碍实现可以依赖一个一致性标记来实现。线程在操作之前,先读取并保存这个标记,在操作完成后,再次读取,检查这个标记是否被更改过,如果两者是一致的,则说明资源访问没有冲突。如果不一致,则说明资源可能在操作过程中与其他线程冲突,需要重试操作。而任何对资源有修改操作的线程,在修改数据前,都需要更新这个一致性标记,表示数据不再安全。数据库中乐观锁,应该比较熟悉,表中需要一个字段v
29、ersion(版本号),每次更新数据version+1,更新的时候将版本号作为条件进行更新,根据更新影响的行数判断更新是否成功,伪代码如下:多个线程更新同一条数据的时候,数据库会对当前数据加锁,同一时刻只有一个线程可以执行更新语句。无锁(Lock-Free)无锁的并行都是无障碍的。在无锁的情况下,所有的线程都能尝试对临界区进行访问,但不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。在无锁的调用中,一个典型的特点是可能会包含一个无穷循环。在这个循环中,线程会不断尝试修改共享变量。如果没有冲突,修改成功,那么程序退出,否则继续尝试修改。但无论如何,无锁的并行总能保证有一个线
30、程是可以胜出的,不至于全军覆没。至于临界区中竞争失败的线程,他们必须不断重试,直到自己获胜。如果运气很不好,总是尝试不成功,则会出现类似饥饿的先写,线程会停止。下面就是一段无锁的示意代码,如果修改不成功,那么循环永远不会停止。无等待 无锁只要求有一个线程可以在有限步内完成操作,而无等待则在无锁的基础上更进一步扩展。它要求所有线程都必须在有限步内完成,这样不会引起饥饿问题。如果限制这个步骤的上限,还可以进一步分解为有界无等待和线程数无关的无等待等几种,他们之间的区别只是对循环次数的限制不同。一种典型的无等待结果就是RCU(Read Copy Update)。它的基本思想是,对数据的读可以不加控制
31、。因此,所有的读线程都是无等待的,它们既不会被锁定等待也不会引起任何冲突。但在写数据的时候,先获取原始数据的副本,接着只修改副本数据(这就是为什么读可以不加控制),修改完成后,在合适的时机回写数据。1.查询数据,此时版本号为w_v2.打开事务3.做一些业务操作4.update t set version=version+1 where id=记录id and version=w_v;/此行会返回影响的行数c5.if(c0)/提交事务 else /回滚事务 while(!atomicVpareAndSet(localVar,localVar+1)localVal=atomicVar.get();
32、公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!更多好文章 1.spring高手系列(正在连载中)2.Java高并发系列(共34篇)3.MySql高手系列(共27篇)4.Maven高手系列(共10篇)5.Mybatis系列(共12篇)6.聊聊db和缓存一致性常见的实现方式7.接口幂等性这么重要,它是什么?怎么实现?8.泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!加微信itsoku,发送:1024,获取 100G 高质量计算机学习视频!第3篇:有关并行的两个重要定律 有关为什么要使用并行程序的问题前面已经进行了简单的探讨。总的来说,最重
33、要的应该是处于两个目的。第一,为了获得更好的性能;第二,由于业务模型的需要,确实需要多个执行实体。在这里,我将更加关注第一种情况,也就是有关性能的问题。将串行程序改造为并发程序,一般来说可以提高程序的整体性能,但是究竟能提高多少,甚至说究竟是否真的可以提高,还是一个需要研究的问题。目前,主要有两个定律对这个问题进行解答,一个是Amdahl定律,另外一个是Gustafson定律。Amdahl(阿姆达尔)定律 Amdahl定律是计算机科学中非常重要的定律。它定义了串行系统并行化后的加速比的计算公式和理论上线。加速比定义:加速比=优化前系统耗时/优化后系统耗时所谓加速比就是优化前耗时与优化后耗时的比
34、值。加速比越高,表明优化效果越明显。图1.8显示了Amdahl公式的推到过程,其中n表示处理器个数,T表示时间,T1表示优化前耗时(也就是只有1个处理器时的耗时),Tn表示使用n个处理器优化后的耗时。F是程序中只能串行执行的比例。公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!根据这个公式,如果CPU处理器数量趋于无穷,那么加速比与系统的串行化比例成反比,如果系统中必须有50%的代码串行执行,那么系统的最大加速比为2。假设有一个程序分为以下步骤执行,每个执行步骤花费100个单位时间。其中,只有步骤2和步骤5可以并行,步骤1、3、4必须串行,如图1.
35、9所示。在全串行的情况下,系统合计耗时为500个单位时间。若步骤2和步骤5并行化,假设在双核处理器上,则有如图1.10所示的处理流程。在这种情况下,步骤2和步骤5的耗时将为50个单位时间。故系统整体耗时为400个单位时间。根据加速比的定义有:加速比=优化前系统耗时/优化后系统耗时=500/400=1.25由于5个步骤中,3个步骤必须串行,因此其串行化比例为3/5=0.6,即 F=0.6,且双核处理器的处理器个数N为2。代入加速比公式得:加速比=1/(0.6+(1-0.6)/2)=1.25在极端情况下,假设并行处理器个数为无穷大,则有如图1.11所示的处理过程。步骤2和步骤5的处理时间趋于0。即
36、使这样,系统整体耗时依然大于300个单位时间。使用加速比计算公式,N趋于无穷大,有加速比=1/F,且F=0.6,故有加速比=1.67。即加速比的极限为500/300=1.67。公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!由此可见,为了提高系统的速度,仅增加CPU处理的数量并不一定能起到有效的作用。需要从根本上修改程序的串行行为,提高系统内可并行化的模块比重,在此基础上,合理增加并行处理器数量,才能以最小的投入,得到最大的加速比。注意:根据Amdahl定律,使用多核CPU对系统进行优化,优化的效果取决于CPU的数量,以及系统中串行化程序的比例。C
37、PU数量越多,串行化比例越低,则优化效果越好。仅提高CPU数量而不降低程序的串行化比例,也无法提高系统的性能。阿姆达尔定律图示为了更好地理解阿姆达尔定律,我会尝试演示这个定定律是如何诞生的。首先,一个程序可以被分割为两部分,一部分为不可并行部分B,一部分为可并行部分1 B。如下图:在顶部被带有分割线的那条直线代表总时间 T(1)。下面你可以看到在并行因子为2的情况下的执行时间:并行因子为3的情况:公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!举个例子一个业务会串行调用2个方法,m1,m2,m1耗时100ms,m2耗时400ms,m2内部串行执行了
38、4个无依赖的任务,每个任务100ms,如下图:m2内部的4个任务无依赖的,即可以并行进行处理,4个任务同时并行,当cpu数量大于等于4的时候,可以让4个任务同时进行,此时m2耗时最小,即100ms,cpu为2个的时候,同时只能够执行2个任务,其他2个任务处于等待cpu分配时间片状态,此时m2耗时200ms;当cpu超过4个的时候,或者趋于无限大的时候,m2耗时还是100ms,此时cpu数量再怎么增加对性能也没有提升了,此时需要提升的是任务可以并行的数量。从阿姆达尔定律可以看出,程序的可并行化部分可以通过使用更多的硬件(更多的线程或CPU)运行更快。对于不可并行化的部分,只能通过优化代码来达到提
39、速的目的。因此,你可以通过优化不可并行化部分来提高你的程序的运行速度和并行能力。你可以对不可并行化在算法上做一点改动,如果有可能,你也可以把一些移到可并行化放的部分。Gustafson定律 Gustafson定律也试图说明处理器个数、串行化比例和加速比之间的关系,如图1.12所示,但是Gustafson定律和Amdahl定律的角度不同。同样,加速比都被定义为优化前的系统耗时除以优化后的系统耗时。公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!根据Gustafson定律,我们可以更容易地发现,如果串行化比例很小,并行化比例很大,那么加速比就是处理器的
40、个数。只要不断地累加处理器,就能获得更快的速度。Amdahl定律和Gustafson定律结论有所不同,并不是说其中有个是错误的,只是二者从不同的角度去看待问题的结果,他们的侧重点有所不同。Amdahl强调:当串行换比例一定时,加速比是有上限的,不管你堆叠多少个CPU参与计算,都不能突破这个上限。Gustafson定律关系的是:如果可被并行化的代码所占比例足够大,那么加速比就能随着CPU的数量线性增长。总的来说,提升性能的方法:想办法提升系统并行的比例,同时增加CPU数量。更多好文章 1.spring高手系列(正在连载中)2.Java高并发系列(共34篇)3.MySql高手系列(共27篇)4.M
41、aven高手系列(共10篇)5.Mybatis系列(共12篇)6.聊聊db和缓存一致性常见的实现方式7.接口幂等性这么重要,它是什么?怎么实现?8.泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!加微信itsoku,发送:1024,获取 100G 高质量计算机学习视频!第4篇:JMM相关的一些概念 JMM(java内存模型),由于并发程序要比串行程序复杂很多,其中一个重要原因是并发程序中数据访问一致性和安全性将会受到严重挑战。如何保证一个线程可以看到正确的数据呢?这个问题看起来很白痴。对于串
42、行程序来说,根本就是小菜一碟,如果你读取一个变量,这个变量的值是1,那么你读取到的一定是1,就是这么简单的问题在并行程序中居然变得复杂起来。事实上,如果不加控制地任由线程胡乱并行,即使原本是1的数值,你也可能读到2。因此我们需要在深入了解并行机制的前提下,再定义一种规则,保证多个线程间可以有小弟,正确地协同工作。而JMM也就是为此而生的。JMM关键技术点都是围绕着多线程的原子性、可见性、有序性来建立的。我们需要先了解这些概念。原子性 原子性是指操作是不可分的,要么全部一起执行,要么不执行。在java中,其表现在对于共享变量的某些操作,是不可分的,必须连续的完成。比如a+,对于共享变量a的操作,
43、实际上会执行3个步骤:1.读取变量a的值,假如a=1 2.a的值+1,为2 3.将2值赋值给变量a,此时a的值应该为2这三个操作中任意一个操作,a的值如果被其他线程篡改了,那么都会出现我们不希望出现的结果。所以必须保证这3个操作是原子性的,在操作a+的过程中,其他线程不会改变a的值,如果在上面的过程中出现其他线程修改了a的值,在满足原子性的原则下,上面的操作应该失败。java中实现原子操作的方法大致有2种:锁机制、无锁CAS机制,后面的章节中会有介绍。可见性 可见性是指一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。有些同学会说修改同一个变量,那肯定是可以看到的,难道线程眼盲了?
44、为什么会出现这种问题呢?看一下java线程内存模型:公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!我们定义的所有变量都储存在主内存中每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)线程对共享变量所有的操作都必须在自己的工作内存中进行,不能直接从主内存中读写(不能越级)不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行。(同级不能相互访问)线程需要修改一个共享变量X,需要先把X从主内存复制一份到线程的工作内存,在自己的工作内存中修改完毕之后,再从工作内存中回写到主
45、内存。如果线程对变量的操作没有刷写回主内存的话,仅仅改变了自己的工作内存的变量的副本,那么对于其他线程来说是不可见的。而如果另一个变量没有读取主内存中的新的值,而是使用旧的值的话,同样的也可以列为不可见。共享变量可见性的实现原理:线程A对共享变量的修改要被线程B及时看到的话,需要进过以下步骤:1.线程A在自己的工作内存中修改变量之后,需要将变量的值刷新到主内存中 2.线程B要把主内存中变量的值更新到工作内存中关于线程可见性的控制,可以使用volatile、synchronized、锁来实现,后面章节会有详细介绍。有序性 有序性指的是程序按照代码的先后顺序执行。为了性能优化,编译器和处理器会进行
46、指令冲排序,有时候会改变程序语句的先后顺序,比如程序。编译器优化后可能变成int a=1;/1int b=20;/2int c=a+b;/3int b=20;/1int a=1;/2int c=a+b;/3公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!上面这个例子中,编译器调整了语句的顺序,但是不影响程序的最终结果。在单例模式的实现上有一种双重检验锁定的方式,代码如下:我们先看instance=new Singleton();未被编译器优化的操作:1.指令1:分配一款内存M2.指令2:在内存M上初始化Singleton对象3.指令3:将M的地址赋
47、值给instance变量编译器优化后的操作指令:1.指令1:分配一块内存S2.指令2:将M的地址赋值给instance变量3.指令3:在内存M上初始化Singleton对象现在有2个线程,刚好执行的代码被编译器优化过,过程如下:最终线程B获取的instance是没有初始化的,此时去使用instance可能会产生一些意想不到的错误。现在比较好的做法就是采用静态内部内的方式实现:public class Singleton static Singleton instance;static Singleton getInstance()if(instance=null)synchronized(Si
48、ngleton.class)if(instance=null)instance=new Singleton();return instance;公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!更多好文章 1.spring高手系列(正在连载中)2.Java高并发系列(共34篇)3.MySql高手系列(共27篇)4.Maven高手系列(共10篇)5.Mybatis系列(共12篇)6.聊聊db和缓存一致性常见的实现方式7.接口幂等性这么重要,它是什么?怎么实现?8.泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!加微信itsoku,发送:102
49、4,获取 100G 高质量计算机学习视频!第5篇:深入理解进程和线程 进程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。进程具有的特征:动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的并发性:任何进程都可以同其他进行一起并发执行独立性:进程是系统进行资源分配和调度的一个独立单位结构性:进程由程序,数据和进程控制块三部分组成public class SingletonDemo private SingletonDemo()priv
50、ate static class SingletonDemoHandler private static SingletonDemo instance=new SingletonDemo();public static SingletonDemo getInstance()return SingletonDemoHandler.instance;公众号:路人甲Java加微信itsoku,发送:1024,获取 10T 高质量计算机学习视频!我们经常使用windows系统,经常会看见.exe后缀的文件,双击这个.exe文件的时候,这个文件中的指令就会被系统加载,那么我们就能得到一个关于这个.exe