并发程序三大要素

2022-03-24 02:39:36 来源:网络整理 作者:管理员

原标题:并发程序三大要素

主题:并发编程三大要素

目标:用例子讲解3要素;刻意练习:细致完整

目标读者:需要了解并发知识的人

并发编程三大要素

并发即多个线程同时运行。所谓一个和尚挑水喝,两个和尚抬水喝,三个和尚没水喝。做事的人一多,就容易出幺蛾子,程序也不例外。所以,为了保证最后结果的正确性,需要保证下面的三大要素。

可见性(visibility)

一个线程对共享变量进行修改,另外的线程能立马看到

先看下面一个小例子:

public class Code3 {

private static /*volatile*/ int num = 0;

public static void main(String[] args) throws InterruptedException {

System.out.println("程序执行");

new Thread(() -> {

System.out.println("子线程开始");

while (num == 0) {

// System.out.println(num);

}

System.out.println("子线程结束");

}).start();

TimeUnit.SECONDS.sleep(1);

num = 1;

}

}

上面程序的执行结果是:

并发程序三大要素

“子线程结束”一直没有输出出来,意味着,对于子线程来说num一直都是等于0的,循环一直没有结束。但我们在主线程,也就是main方法里明明把num改成1了呀。为什么会这样呢?

这是因为线程在执行的时候会读取出一份共享变量的拷贝到线程本地的缓存中,所以线程们对这个变量的修改,互相之间是不可见的。

解决这个问题的一个办法,就是给变量加上volatile关键字,这个关键字的作用之一,就是保证变量的更新,对所有的线程都是可见的。

我在第9行注释掉的那句打印,也可以解决可见性的问题,因为println()方法里加了synchronized,它也能变量的可见性。

并发程序三大要素

有序性(ordering)

程序执行的顺序和代码的顺序保持一致

程序在实际的执行过程中,不一定是严格按照代码的顺序执行的。

为了提高效率,可能会发生指令重排。

比如,有两句话

  1. 等待用户输入变量y的值
  2. 计算x+1

因为CPU的执行速度很快,在等待语句1执行的过程中,我可以先把语句2给算出来。而不是空在那里等着,因为两句话没什么前后关联。

当然,如果两句话换成了

  1. 等待用户输入变量x的值
  2. 计算x+1

这就肯定不能重排了。

所以,对于单线程来说,就有一个特性叫as if serial,像是顺序执行一样。只要保证单线程结果的最终一致性就可以了。

但,对于多线程来说就可能出现问题。比如下面这个程序。

public class Code3 {

private static int num = 0;

private static volatile boolean flag = true;

public static void main(String[] args) throws InterruptedException {

System.out.println("程序执行");

new Thread(() -> {

System.out.println("子线程开始");

while (flag) {

}

System.out.println(num);

}).start();

TimeUnit.SECONDS.sleep(1);

num = 1;

flag = false;

}

}

因为15行和16行可能出现重排的现象,flag=false先执行,再执行num=1,就可能导致最终11行子线程输出的num值为0,而不是1。

当然这个做实验做很多次也不一定能做得出来,只是有可能发生。

解决这个问题已经可以使用volatile关键字,volatile的另一个作用就是禁止重排序。

具体的实现机制是增加内存屏障(Memory Barrier)

  • LoadLoadBarrier,load1;屏障;load2,即屏障的前后都是读指令,则load2必须等待load1执行完毕。
  • StoreStoreBarrier,store1;屏障;store2,即屏障的前后都是写指令,则store2必须等待store1执行完毕。
  • LoadStoreBarrier,load;屏障;store,即屏障的前是读指令,屏障后是写指令,则store必须等待load执行完毕。
  • StoreLoadBarrier,store;屏障;load,即屏障的前是写指令,屏障后是读指令,则load必须等待store执行完毕。
  1. 写屏障(即volatile写之前都不能写,volatile写之后才可以读) StoreStoreBarrier volatile写(store) StoreLoadBarrier
  2. 读屏障(即volatile读之后,才可以读写) volatile读(load) LoadLoadBarrier LoadStoreBarrire

原子性(atomicity)

不可分割的操作,要么都成功,要么都失败

还是先来一段小代码:

public class Code3 {

private static volatile int num = 0;

public static void main(String[] args) throws InterruptedException {

Thread[] threads = new Thread[100];

CountDownLatch countDownLatch = new CountDownLatch(threads.length);

for (int i = 0; i < threads.length; i++) {

threads[i] = new Thread(() -> {

for (int j = 0; j < 100; j++) {

num++;

}

countDownLatch.countDown();

});

}

for (Thread thread : threads) {

thread.start();

}

countDownLatch.await();

System.out.println(num);

}

}

起100个线程,同时对num这个变量做100次自增操作,理想的结果应该是100*100=10000。但我的机器上测试,实际的结果是9000多。

说明一个什么问题呢,就是自增这个操作不是原子性的,因为它可能中间过程被打断。

假设自增有三步:

  1. 把num值取出来
  2. 把num值加一
  3. 把num值放回去

就可能出现,当前num=0,线程1把0取出来了,并且完成了加一,把值变成了1,这时候线程2也来了,它取出来的也是0,并且把值从0改成1,并且把1的值写了回去。然后这时候线程1开始执行第3步,又一次把1写了回去。这就导致了数据不一致的结果。如果自增操作不可打算的话,两个线程执行完的结果应该是2,而不是1。

解决这个问题的办法,就是上锁。

上锁的本质:让并发的程序序列化,即把原本同时执行的程序,改成前后顺序执行。

悲观锁

认为这个操作一定会被打断,所以不管三七二十一,先锁上再说。通过synchronized实现。(第10行)

public class Code3 {

private static volatile int num = 0;

public static void main(String[] args) throws InterruptedException {

Thread[] threads = new Thread[100];

CountDownLatch countDownLatch = new CountDownLatch(threads.length);

for (int i = 0; i < threads.length; i++) {

threads[i] = new Thread(() -> {

for (int j = 0; j < 100; j++) {

synchronized (Code3.class) {

num++;

}

}

countDownLatch.countDown();

});

}

for (Thread thread : threads) {

thread.start();

}

countDownLatch.await();

System.out.println(num);

}

}

乐观锁

认为这个操作不会被打断,所以先不上锁,在写入的时候验证原数据是否被修改,如果被修改了,就读取新的值,再重试一遍,直到成功为止。通过CAS(Compare And Swap/Set)实现。

java自带有CAS方式的整形类AtomicInteger

public class Code3 {

private static AtomicInteger num = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {

Thread[] threads = new Thread[100];

CountDownLatch countDownLatch = new CountDownLatch(threads.length);

for (int i = 0; i < threads.length; i++) {

threads[i] = new Thread(() -> {

for (int j = 0; j < 100; j++) {

num.incrementAndGet();

}

countDownLatch.countDown();

});

}

for (Thread thread : threads) {

thread.start();

}

countDownLatch.await();

System.out.println(num);

}

}

锁类型的选择

并不是乐观锁看着名字比较积极就无脑选择乐观锁比较好。

因为乐观锁会一直频繁的重试,直到成功为止,这个重试的过程也是会消耗cpu资源的。

而悲观锁通过等待队列的方式实现,在等待锁的过程中不消耗资源,所以可以视情况而定。

如果锁内部执行的时间较长,且排队人数很多,就可以选择悲观锁。

如果锁内部执行时间很多,且排队人数不多,就可以选择乐观锁。

字数:不统计

耗时:2小时45分

··················END··················

显示全文
为您推荐
紫禁城珍妃坠井之谜,珍妃死因真相揭秘(被慈溪太后投井)
紫禁城珍妃坠井之谜,珍妃死因真相揭秘(被慈溪太后投井)

【导读】 紫禁城珍妃坠井之谜,珍妃死因真相揭秘(被慈溪太后投井),下面是小编为你收集整理的,希望对你有帮助!中国五千年“男尊女卑”的封建思想,在皇宫中发挥的淋淋尽致,光绪皇帝的妃子珍妃却截然不同。她天生活波开朗,但也就是因为张扬的性格让她葬身井底,珍妃坠井之谜世人议论纷纷,珍妃井灵异......

发布时间:2023-06-08 16:01:12

红崖天书的内容是什么?红崖天书之谜真的被林国恩解开了?
红崖天书的内容是什么?红崖天书之谜真的被林国恩解开了?

【导读】 红崖天书的内容是什么?红崖天书之谜真的被林国恩解开了?,下面是小编为你收集整理的,希望对你有帮助!说到“红崖天书”其实大家知道的时候应该是在前几年的时候,发布的一则悬赏吧,那就是说能解开红崖天书的秘密,那么这个人就能获得100万的奖励,当然了刚好有一位叫林国恩的老先生好像解开了,那么这件事情到底是......

发布时间:2023-06-08 15:01:17

即将上线的五部剧,部部未播先火,有你期待已久的吗?
即将上线的五部剧,部部未播先火,有你期待已久的吗?

新的一年开始,随着《王牌部队》,《沉睡花园》,《三生有幸遇上你》,《输赢》等剧抢占先机播出后,无论是口碑还是收视率都极佳,颇受好评不断。令不少未播剧按捺不住,纷纷宣布定档时间,那么接下来一起来看看,即.........

发布时间:2023-06-08 14:06:15

地球到月球的距离:38.4万千米(月球三大谜团)
地球到月球的距离:38.4万千米(月球三大谜团)

【导读】 地球到月球的距离:38.4万千米(月球三大谜团),下面是小编为你收集整理的,希望对你有帮助!地球到月球的距离约为38.4千米,众所周知月球是地球唯一的自然卫星,月球自从形成以来一直围绕着地球做着公转运动。自古以来人类就对月球产生了美好的遐想,人们一直认为月球上存在着美丽的嫦娥,嫦娥是一位单纯......

发布时间:2023-06-08 14:01:12

张柏芝坐秋千椅拍写真 休闲造型演绎别样潮酷风
张柏芝坐秋千椅拍写真 休闲造型演绎别样潮酷风

近日,张柏芝工作室分享了一组她的最新美图。照片中,张柏芝身穿白色休闲套装,简约运动风营造出休闲假日风。她坐在金属秋千椅上,自在随性,披肩长发随风轻扬,演绎别样运动时尚。.........

发布时间:2023-06-08 13:05:15

五道口职业技术学校是什么意思 其实是数一数二的大学
五道口职业技术学校是什么意思 其实是数一数二的大学

【导读】 五道口职业技术学校是什么意思 其实是数一数二的大学,下面是小编为你收集整理的,希望对你有帮助!在很多人心目中职业学校都是不好的学校,但是其实不一定是这样的,比如说五道口职业技术学校。那么这是一个什么学校呢,下面和小编一起了解探索下吧。五道口职业技术学校是什么意思中国很多高校除了本身的正经名字之......

发布时间:2023-06-08 13:01:06

张天爱封面大片 率性果敢展时尚张力
张天爱封面大片 率性果敢展时尚张力

近日,演员张天爱为某时尚杂志拍摄的五月刊封面大片正式曝光。照片中,张天爱长发披肩,额间细碎凌乱的发丝尽显慵懒神秘,无论是对身材有高要求的长款修身裙,还是简约俏皮的OL风无袖马甲,亦或是休闲复古的白色连.........

发布时间:2023-06-08 12:06:17

卡西欧snz的意思是什么
卡西欧snz的意思是什么

【导读】 卡西欧snz的意思是什么,下面是小编为你收集整理的,希望对你有帮助!卡西欧snz是什么意思卡西欧snz是间歇闹钟,也叫打盹闹钟,打盹闹铃一般闹铃时间到时,手表会鸣音几秒,然后隔几分钟一次,具体时间长度视各款手表而定。 一般最多响7次,中途可以选择关闭。卡西欧snz闹钟......

发布时间:2023-06-08 12:01:17

秦昊:演员不是来讨喜欢的
秦昊:演员不是来讨喜欢的

中新文娱4月27日电(记者 郎朗)在一段独白和上帝视角的空镜中,《亲爱的小孩》,这部从开播到结局都伴随着热议的现实题材作品迎来了大结局。有人能与剧中的人物共情,感慨生活的糟心;也有人认为其中展现生活的.........

发布时间:2023-06-08 11:05:30

煮熟的土豆怎么做好吃又简单
煮熟的土豆怎么做好吃又简单

【导读】 煮熟的土豆怎么做好吃又简单,下面是小编为你收集整理的,希望对你有帮助!1、锅内倒油,大火烧开待油温7成热下土豆开始炸,土豆放进去后用中小火慢慢炸。2、炸到外皮焦黄锅铲轻轻碰一下感觉外皮酥脆即可捞出。3、粗细辣椒粉各一大勺,葱花两根,大蒜三瓣,全部切碎倒入碗中,放一勺盐,......

发布时间:2023-06-08 11:01:07

席琳迪翁因病取消所有巡演
席琳迪翁因病取消所有巡演

55岁的流行天后席琳·迪翁在上一年12月宣告罹患了僵人综合征(SPS),这是一种不治之症,患者最终会由于躯干弯曲和四肢僵硬失掉行走能力。5月26日报导,这种疾病难以发现、难以确诊,并且现在没有特效药可.........

发布时间:2023-06-08 10:07:40

喀纳斯湖水怪真相大白,长15米重32吨的新型物种
喀纳斯湖水怪真相大白,长15米重32吨的新型物种

【导读】 喀纳斯湖水怪真相大白,长15米重32吨的新型物种,下面是小编为你收集整理的,希望对你有帮助!在中国的各大淡水湖中有着不少关于水怪的传闻,像青海湖水怪,铜山湖水怪。而在新疆北部的喀纳斯湖中也有喀纳斯湖水怪,当地牧民一直传说湖中有巨型“水怪”,常常将在湖边饮水的马匹拖入水......

发布时间:2023-06-08 10:01:13