java高并发编程(一)——了解并发编程

并发编程笔记和备忘

记录学习并发编程过程中的的笔记和理解,有些实例出自自我总结和书本知识,有错误的地方希望前辈们指出。万分感谢。

一、并发编程的几个重要概念

(1)“同步”(Synchronous)和“异步”(Asynchronous)
  “同步”方法的调用,调用者必须等到方法调用返回后才能继续后续的操作。‘’异步”方法调用之后立即返回,调用者可以继续后续的操作。两者的差别可以用生活中的实体店购物(对应同步)和网购(对应异步)来体现。实体店购物,在我们对店家说想买某件商品比如衣服之后,我们就一直在店里等待老板拿出衣服,包装好,然后付款,再回家这一整个过程。而网购在选好商品下单之后,我们就可以自由的处理其他事,只要等快递上门,签收就行了。
(2)“并发”(Concurrency) 和 “并行”(Parallelism)
  “并发”和“并行”都表示的是多个任务一起执行。然而“并发”侧重的是多个任务交替进行,这些任务之间可能还是串行的,并非真正意义上的同时执行。“并行”表示的是多个任务真正的同时进行。两者好比写作业时交头接耳。“并发”同学一边写作业一边和同桌聊天,但是聊的很投入,导致在讲话的时候就把笔放下了,在写作业的时候却没有说话。“并行”同学一心二用,右手奋笔疾书,嘴里同时还滔滔不绝。课后纪录委员报告老师的时候说的是这两个同学上课一边写作业一边聊天。
(3)“临界区”
  临界区表示的是可以被多个线程使用的公共资源,然而每次只有一个线程可以得到它的使用权。临界区一旦被占用,其他线程想要获取它的使用权就只能等待。
(4)“阻塞”(Blocking)和“非阻塞”(Non-Blocking)
  阻塞即请求之后,数据未准备好,不立即返回,需要等待。非阻塞即数据未准备好,但是立即返回。
(5)“死锁”(Deadlock)、“饥饿”(Starvation)和“活锁”(Livelock)
  死锁指的是多个线程期望获取对方的锁,但是自身又占用对方需要的锁,若没有外力的干预则这些线程一直处于等待状态,形成死锁。死锁对应的典型事例就是四辆行驶方向不同的汽车,各自堵住了道路如下图所示在这里插入图片描述
  饥饿指的是线程一直获取不到期望获取的资源。由于某些线程的高优先级导致低优先级的线程一直获取不到所需资源即造成了饥饿。好比一家极受欢迎的面包店,顾客只能通过争抢才能买到面包,那么强壮的人一直可以买到面包,但是瘦弱的人就一直买不到了。
  活锁指的是线程拿到了资源,却都释放不执行,导致了资源在多个线程之间不停跳动,但是又没有执行。好比哥哥弟弟吃苹果,哥哥觉得弟弟小拿了苹果却想要给弟弟吃,弟弟觉得哥哥是长辈所以要哥哥吃,所以弟弟拿到哥哥给的苹果之后,又马上给了哥哥,这样,苹果在哥哥弟弟手里不断的给来给去,却一个人都没有吃苹果。

二、并发级别

(1)阻塞(Blocking)
  一个线程阻塞的,在其他资源没有释放资源的之前,都无法继续执行
(2)无饥饿(Starvation-Free)
  由于线程具有优先级差别,资源就会出现分配不均的现象。对于非公平的锁而言,系统允许其插队,这样就有可能导致低优先级的线程产生饥饿。如果锁是公平的,高优先级的线程也要按先来后到的顺序进行,相当于不管你身份多NB,就是不许插队的意思。那么所有线程就都有机会执行
(3)无障碍(Obstruction-Free)
  无障碍的线程执行时,不会因为临界区的问题而导致另一方被挂起,大家一起对共享数据进行操作,但是同时操作会导致一系列问题,比如两线程都对其进行修改,那数据有可能改的面目全非。一旦发现线程之间的冲突,线程自身会立即回滚自己的操作,保证数据安全。然而如果两个线程所操作的数据并没有发生冲突,那么线程会各自完成自己的工作,释放资源。然而当线程数量多,产生冲突过多的时候,没有一个线程可以走出临界区,所有的线程可能都在不断的回滚自己的操作,这种情况明显是不符合预期的。
  一种可行的无障碍的实现方式是在共享资源中设置一个一致性标记。所有的线程修改数据的时候都要事先修改这个一致性标记。线程工作时先读取并保存它,在操作完成后再次读取,如果两者一致,则资源没有冲突,如果不一致,则说明可能与其他线程存在冲突。
(4)无锁(Lock-Free)
  无锁的并行都是无障碍的。但是无锁的并发保证必然有一个线程能在有限的工作步骤内执行完成。
(5)无等待
  无等待要求在无锁的基础上,所有的线程都在有限的步骤之内完成,这样就不会引起饥饿问题。一种典型的无等待结构就是RCU(Read-Copy-Update)。对数据进行读取的时候不加以控制,所有的线程都是无等待的。但是在写数据的时候,先获取数据的副本,在副本上修改,在适当的时机回写数据。

三、三大特性

(1)原子性
  原子性是指一个操作是不可中断的,即多个线程一起执行的时候,一个操作一旦执行,就不会被其他线程干扰。比如,两个线程同时对int i 赋值,程序结束后i的值要么是1要么是-1。两线程之间是没有干扰的。
(2)可见性
  可见性指的是当一个线程修改了共享资源的数据之后,其他的线程能否立刻知道这个修改。在并行的程序中,可见性问题是存在的。比如当cpu1和cpu2各有一个线程同时对一个int i进行操作时,cpu1将i进行了优化,并存在缓存中或者寄存器中,cpu2的线程对i进行修改,那么cpu1可能并不能马上能得知这一修改,读取到的依旧是旧值。
(3)有序性
  当并发程序执行时,程序的执行顺序可能并不是按照顺序,从前往后执行的,写在前面的程序可能在之后执行。在java里可以通过volatile来保证一定的有序性,另外也可以通过synchroized和lock来保证有序性。synchroized和lock是保证每个时刻是只有一个线程执行同步代码,相当于是让线程顺序执行代码从而保证有序性(单线程看有序,不会改变最终的结果,但是多线程情况下就不一定了)。Java具备一些先天的有序性(不需要任何手段就能保证有序性),即happens-before原则(先行发生原则)。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证有序性。虚拟机可以随意的对它们进行重排序。

猜你喜欢

转载自blog.csdn.net/qq_34459728/article/details/87861151
今日推荐