Java并发包之闭锁/栅栏/信号量及并发模型和锁

threadLocal能够为每一个线程维护变量副本,常用于在多线程中用空间换时间

进程死锁:进程死锁,指多个进程循环等待他方占有的资源而一直等待下去的局面;

 进程活锁:线程1,2需要同时占有a,b才可以,1占有a,2占有b,为了避免死锁,1,2分别释放,a,b空闲,此时1,2又同时抢锁,发生活锁;(电梯上遇到人,一个进一个出,2个人同时往一个方向让路,来回反复)

进程饥饿:指某一个或多个线程因为各种原因无法获取所需要的资源,导致一直无法执行;

一、乐观锁

 总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。 乐观锁适用于多读的应用类型,这样可以提高吞吐量

 version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

核心SQL代码:

update table set x=x+1, version=version+1 where id=#{id} and version=#{version};  

 CAS操作方式:即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。

一、悲观锁

 总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。 共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。

对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

  。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞竞争切换后继续竞争锁稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

    传统的并发模型:每个I/O流都有一个新的线程管理;

    I/O多路复用:只有单个线程,通过跟踪每个I/O流的状态,来管理多个I/O流;

    (我们都redis-client在操作的时候,会产生具有不同事件类型的socket。在服务端,有一段I/O多路复用程序,将其置入队列之中,然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中)

 

Java并发包之闭锁/栅栏/信号量

Java并发包之闭锁/栅栏/信号量

1.提供内存可见性和防止指令重排的volatile属于jvm关键字

2.而java.util.concurrent包(J.U.C)中包含的是java并发编程中有用的一些工具类,包括几个部分: 

  • locks部分:包含在java.util.concurrent.locks包中,提供显式锁(互斥锁和速写锁)相关功能。
  • atomic部分:包含在java.util.concurrent.atomic包中,提供原子变量类相关的功能,是构建非阻塞算法的基础。
  • executor部分:散落在java.util.concurrent包中,提供线程池相关的功能。
  • collections部分:散落在java.util.concurrent包中,提供并发容器相关功能。
  • tools部分:散落在java.util.concurrent包中,提供同步工具类,如信号量、闭锁、栅栏等功能。 

1、Semaphore信号量:跟锁机制存在一定的相似性,semaphore也是一种锁机制,所不同的是,reentrantLock是只允许一个线程获得锁,而信号量持有多个许可(permits),允许多个线程获得许可并执行。可以用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。

2、CountDownLatch闭锁:允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

    主要方法: 

        1. CountDownLatch.await():将某个线程阻塞住,直到计数器count=0才恢复执行。 

        2. CountDownLatch.countDown():将计数器count减1。

3、CyclicBarrier栅栏:用于阻塞一组线程直到某个事件发生。所有线程必须同时到达栅栏位置才能继续执行下一步操作,且能够被重置以达到重复利用。而闭锁是一次性对象,一旦进入终止状态,就不能被重置。

     闭锁用于一组线程等待(阻塞)一个外部事件的发生,这个事件发生之前这些线程阻塞,等待控制线程打开闭锁,然后这些线程同时开始执行。闭锁强调的是阻塞后的同时开始;

栅栏则是一组线程相互等待,直到所有线程都到达某一点时才打开栅栏,然后线程可以继续执行,也就是说控制线程先设置一个时间点,然后这些线程各自执行,执行完等待(阻塞),直到这组线程中的所有线程执行完,然后控制线程栅栏打开,这些线程同时继续执行。栅栏强调的是各自执行完后的相互等待以及继续执行。

信号量根据一个计数器控制一个结果的数量,条件满足情况下才能进行增加和移除操作,否则进行操作的线程阻塞。

场景对比:

l  闭锁场景:几个人相约去公园游玩,在家做好准备,约定在某一时刻同时出发去公园,准备工作进行的快的不能提前出门,到点出门。

l  栅栏场景:几个人相约去公园游玩,几个人去到公园门口,要等全部到达公园门口后才一起进入公园。

l  信号量场景:几个人相约去公园游玩,等大家都到公园后,发现来的太迟了,公园游客饱和,公园限制入场游客的数量。游客在门口等待,出来一人,再进入一人,只能一个一个进入。

缓存

页面缓存经常用在CMS(content manage system)内存管理系统里面。

数据缓存经常会用在页面的具体数据里面。

页面缓存(smarty静态化技术)

第一次从数据库读取,然后生成一个静态页面,以后所有的读取,只加载这个静态页面就可以了‘’

数据缓存

由于一个页面有好几种需要从不同的缓存中读取数据的模块,所以不适合使用页面缓存

猜你喜欢

转载自blog.csdn.net/qq_21325705/article/details/84306748