精华内容
下载资源
问答
  • 前言碎语Synchronized和 ReentrantLock 大家应该都不陌生了,作为java中最常用的本地,最初版本中 ReentrantLock 的性能远远强于 Synchronized 的,后续java在一次次的版本迭代中 对 Synchronized 进行了大量的...

    前言碎语

    Synchronized和 ReentrantLock 大家应该都不陌生了,作为java中最常用的本地锁,最初版本中 ReentrantLock 的性能是远远强于 Synchronized 的,后续java在一次次的版本迭代中 对 Synchronized 进行了大量的优化,直到 jdk1.6 之后,两种锁的性能已经相差无几,甚至 Synchronized 的自动释放锁会更好用。

    在面试时被问到 Synchronized 和 ReentrantLock 的使用选择时,很多朋友都脱口而出的说用 Synchronized ,甚至在我面试的时候问面试者,也很少有人能够答出所以然来,moon 想说,这可不一定, 只对标题感兴趣的同学可以直接划到最后 ,我可不是标题党~

    Synchronized使用

    在 java 代码中 synchronized 的使用 是非常简单的

    • 1.直接贴在方法上(锁住的是当前类的字节码文件)
    • 2.贴在代码块儿上(锁住的是对象)
    273f028070f00d28136998343ae4af49.png

    程序运行期间,Synchronized那一块儿代码发生么什么?

    来看一张图

    8d40171eab0a9fcf4b49f7e115d94c9e.png

    在多线程运行过程中, 线程会去先抢对象的监视器 ,这个监视器是对象独有的,其实就相当于一把钥匙,抢到了,那你就获得了当前代码块儿的执行权。

    其他没有抢到的线程会进入队列(SynchronizedQueue)当中等待,等待当前线程执行完后,释放锁.

    最后当前线程执行完毕后通知出队然后继续重复当前过程.

    从 jvm 的角度来看 monitorenter 和 monitorexit 指令代表着代码的执行与结束 。

    SynchronizedQueue:

    SynchronizedQueue 是一个比较特殊的队列,它没有存储功能,它的功能就是维护一组线程,其中每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。因此此队列内部其 实没有任何一个元素,或者说容量是0,严格说并不是一种容器。由于队列没有容量,因此不能调用 peek 操作,因为只有移除元素时才有元素。

    举个例子:

    喝酒的时候, 先把酒倒入酒盅,然后再倒入酒杯,这就是正常的队列

    喝酒的时候, 把酒直接倒入酒杯,这就是 SynchronizedQueue

    这个例子应该很清晰易懂了,它的好处就是可以直接传递,省去了一个第三方传递的过程。

    聊聊细节,锁升级的过程

    在 jdk1.6 以前,Synchronized 是一个重量级锁,还是先贴一张图

    bdd27d85d3569c6dbf18c435146b490e.png

    这就是为什么说,Synchronized 是一个重量级锁的原因, 因为每一次锁的资源都是直接和 cpu 去申请的,而 cpu 的锁数量是固定的 ,当 cpu 锁资源使用完后还会进行锁等待,这是一个非常耗时的操作。

    但是在jdk1.6,针对代码层面进行了大量的优化,也就是我们常说的锁升级的过程。

    591f61bf514c7ed9e4da72cf68cb6ffc.png

    这就是一个 锁升级的过程 ,我们简单的说说:

    • 无锁:对象一开始就是无锁状态。
    • 偏向锁: 相当于给对象贴了一个标签 (将自己的线程 id 存入对象头中),下次我再进来时,发现标签是我的,我就可以继续使用了。
    • 自旋锁: 想象一下有一个厕所,里面有一个人在,你很想上但是只有一个坑位,所以你只能徘徊等待,等那个人出来以后,你就可以使用了 。 这个自旋是使用 cas 来保证原子性的,关于 cas 我这里就不再赘述了。
    • 重量级锁: 直接向 cpu 去申请申请锁 ,其他的线程都进入队列中等待。

    锁升级是什么时候发生的?

    • 偏向锁:一个线程获取锁时会由无锁升级为偏向锁
    • 自旋锁:当产生线程竞争时由偏向锁升级为自旋锁,想象一下 while(true) ;
    • 重量级锁:当线程竞争到达一定数量或超过一定时间时,晋升为重量级锁

    锁的信息是记录在哪里的?

    f988da19f1f87b6fbeb265e97895aadc.png

    这张图是对象头中 markword 的数据结构 ,锁的信息就是在这里存放的,很清楚的表明了锁在升级的时候锁信息的变动, 其实就是通过二进制的数值,来对对象进行一个标记,每个数值代表一种状态 。

    既然synchronized有锁升级那么有锁降级吗?

    这个问题和我们的题目就有很大的关联了。

    在 HotSpot 虚拟机中是有锁降级的, 但是仅仅只发生在 STW 的时候 ,只有垃圾回收线程能够观测到它,也就是说, 在我们正常使用的过程中是不会发生锁降级的,只有在 GC 的时候才会降级。

    所以题目的答案,你懂了吗?哈哈,我们接着往下走。

    ReentrantLock的使用

    1ce679c5e02f74a4faed8314df5b0d1d.png

    ReentrantLock 的使用也是非常简单的,与 Synchronized 的不同就是需要自己去手动释放锁,为了保证一定释放,所以通常都是和 try~finally 配合使用的。

    ReentrantLock的原理

    ReentrantLock 意为 可重入锁 ,说起 ReentrantLock 就不得不说 AQS ,因为其 底层就是使用 AQS 去实现的。

    ReentrantLock有 两种模式 ,一种是公平锁,一种是非公平锁。

    • 公平模式下等待线程入队列后会严格按照队列顺序去执行
    • 非公平模式下等待线程入队列后有可能会出现插队情况
    321017ea8fc5687b637166fa4fa4b8d7.png

    这就是ReentrantLock的结构图,我们看这张图其实是很简单的,因为主要的实现都交给AQS去做了,我们下面着重聊一下AQS。

    AQS

    AQS(AbstractQueuedSynchronizer): AQS 可以理解为就是 一个可以实现锁的框架

    简单的流程理解:

    公平锁:

    90416240b768dffd3bc478cf7b4b5884.png
    • 第一步:获取状态的 state 的值。如果 state=0 即代表锁没有被其它线程占用,执行第二步。如果 state!=0 则代表锁正在被其它线程占用,执行第三步。
    • 第二步:判断队列中是否有线程在排队等待。如果不存在则直接将锁的所有者设置成当前线程,且更新状态 state 。如果存在就入队。
    • 第三步:判断锁的所有者是不是当前线程。如果是则更新状态 state 的值。如果不是,线程进入队列排队等待。

    非公平锁:

    f6acda349870ad1383967f8beb80ef6c.png
    • 第一步:获取状态的 state 的值。如果 state=0 即代表锁没有被其它线程占用,则设置当前锁的持有者为当前线程,该操作用 CAS 完成。如果不为0或者设置失败,代表锁被占用进行下一步。
    • 此时获取 state 的值,如果是0,代表刚好线程释放了锁,此时将锁的持有者设为自己如果不是0,则查看线程持有者是不是自己如果是,则给state+1,获取锁如果不是,则进入队列等待

    读完以上的部分相信你对AQS已经有了一个比较清楚的概念了,所以我们来聊聊小细节。

    AQS使用state同步状态(0代表无锁,1代表有),并暴露出 getState 、 setState 以及 compareAndSet 操作来读取和更新这个状态,使得仅当同步状态拥有一个期望值的时候,才会被原子地设置成新值。

    当有线程获取锁失败后,AQS是通过一个 双向的同步队列 来完成同步状态的管理,就被添加到队列末尾。

    d14c19560c75f087b2b1b95f8a5f2369.png

    这是定义头尾节点的代码,我们可以先使用 volatile 去修饰的,就是保证让其他线程可见, AQS 实际上就是修改头尾两个节点来完成入队和出队操作的。

    AQS 在锁的获取时,并不一定只有一个线程才能持有这个锁,所以此时有了 独占模式和共享模式 的区别,我们本篇文章中的 ReentrantLock 使用的就是独占模式,在多线程的情况下只会有一个线程获取锁。

    40e0b1110a41f5a21511ca37721b25ba.png

    独占模式的流程是比较简单的,就根据state是否为0来判断是否有线程已经获得了锁,没有就阻塞,有就继续执行后续代码逻辑。

    0ff6bcb389c5d034d49f443eec9ae122.png

    共享模式的流程根据state是否大于0来判断是否有线程已经获得了锁,如果不大于0,就阻塞,如果大于0,通过CAS的原子操作来自减state的值,然后继续执行后续代码逻辑。

    ReentrantLock和Synchronized的区别

    • 其实ReentrantLock和Synchronized 最核心的区别就在于 Synchronized适合于并发竞争低的情况,因为Synchronized的锁升级如果最终升级为重量级锁在使用的过程中是没有办法消除的,意味着每次都要和cpu去请求锁资源,而ReentrantLock主要是提供了阻塞的能力, 通过在高并发下线程的挂起,来减少竞争,提高并发能力 ,所以我们文章标题的答案,也就显而易见了。
    • synchronized是一个关键字,是由 jvm层面 去实现的,而ReentrantLock是由 java api 去实现的。
    • synchronized是隐式锁,可以 自动释放锁 ,ReentrantLock是显式锁,需要 手动释放锁
    • ReentrantLock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去, 不能够响应中断。
    • ReentrantLock可以获取锁状态,而synchronized不能。

    说说标题的答案

    其实题目的答案就在上一栏目的第一条,也是核心的区别,synchronized升级为重量级锁后无法在正常情况下完成降级,而ReentrantLock是通过阻塞来提高性能的,在设计模式上就体现出了对多线程情况的支持。

    来源:https://www.tuicool.com/articles/A36jUbf

    展开全文
  • 进程也可能整个程序或者部分程序的动态执行。线程一组指令的集合,或者程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上轻量级的进程,它负责在单个程序里执行多...

    线程的概念:

    • 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。
    • 线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.
    • 线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈程序计数器为其执行上下文.多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定. 线程的运行中需要使用计算机的内存资源和CPU。

    多线程的概念

    • 多线程是指从软件或者硬件上实现多个线程并发执行的技术.
    • 多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。
    • 最简单的比喻多线程就像火车的每一节车厢,而进程则是火车。车厢离开火车是无法跑动的,同理火车也不可能只有一节车厢。多线程的出现就是为了提高效率。

    如果你的应用程序需要采取以下的操作,那么你尽可在编程的时候考虑多线程机制:

    • 连续的操作,需要花费忍无可忍的过长时间才可能完成
    • 并行计算
    • 为了等待网络、文件系统、用户或其他I/O响应而耗费大量的执行时间
    • 所以说,在动手之前,先保证自己的应用程序中是否出现了以上3种情形。

    为什么需要多线程(解释何时考虑使用线程)

    • 从用户的角度考虑,就是为了得到更好的系统服务;从程序自身的角度考虑,就是使目标任务能够尽可能快的完成,更有效的利用系统资源。综合考虑,一般以下场合需要使用多线程:
    • 程序包含复杂的计算任务时,主要是利用多线程获取更多的CPU时间(资源)。
    • 处理速度较慢的外围设备.比如:打印时。再比如网络程序,涉及数据包的收发,时间因素不定。使用独立的线程处理这些任务,可使程序无需专门等待结果。
    • 程序设计自身的需要.WINDOWS系统是基于消息循环的抢占式多任务系统,为使消息循环系统不至于阻塞,程序需要多个线程的来共同完成某些任务。
    • 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行

    线程的优先级

    • 优先级的取值为1-10(数值越高优先级越高)。
    • Public final int getPriority();  得到线程优先级的数值。
    • Public final void setPriority(int newPriority);修改线程的优先级。
    • 注:优先级高不代表该线程就一定先运行,只能代表该线程先运行的可能型比较大。

    控制线程周期常用的方法

    • Wait()释放CPU的执行权,释放锁。
    • Notify()回到wait前的状态。
    • Yied()让线程临时暂停。(让线程将资源释放出来)
    • Join()让该线程强行加入执行。
    • SetDaemon(true)设置该线程为后台线程(当前台线程结束时,后台线程一定会一起结束)。
    • 注:结束线程原理就是让run方法结束,所以只要控制run的流程即可。

    为什么要线程同步

    • 线程间共享代码和数据可以节省系统开销,提高效率。但也同时会导致“数据访问冲突”。如何实现线程间有机交互,并确保共享资源在某时只能被一个线程访问,就是线程同步。
    •   多个线程间共享的数据称为临界资源。

    注意: 代码中如果没有pthread_join,主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。

    多线程的同步与互斥:

    方式一:锁

    • 在主线程中初始化锁为解锁状态
      • pthread_mutex_t mutex;
      • pthread_mutex_init(&mutex, NULL);
    • 在编译时初始化锁为解锁状态
      • 锁初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    • 访问对象时的加锁操作与解锁操作
      • 加锁 pthread_mutex_lock(&mutex)
      • 释放锁 pthread_mutex_unlock(&mutex)

    互斥锁

    •   每个对象都对应一个互斥锁标记,可以保证在某一时刻只能有一个线程访问该对象。
    •   互斥锁的关键字 synchronized 可以写在某个方法上(代表锁调用该方法的对象);  可以括在要锁的语句外。
    • 好处:解决了线程安全的问题
    • 弊端:降低了运行效率(判断锁,且不能共享信息);容易出现死锁。

    死锁:

    • 两个线程A,B用到同一个对象s(s为共享资源),且线程A在执行中要用到B运行后所创造条件。在这种前提下A先开始运行,进入同步块后,对象s被锁定,接着线程A因等待B运行结束而进入阻塞状态,于是B开始运行,但因无法访问对象s,线程B也进入阻塞状态,等待s被线程A解锁。最终的结果:两个线程互相等待,都无法运行。

    方式二:信号量

    锁有一个很明显的缺点,那就是它只有两种状态:锁定与不锁定。

    信号量本质上是一个非负数的整数计数器,它也被用来控制对公共资源的访问。当公共资源增加的时候,调用信号量增加函数sem_post()对其进行增加,当公共资源减少的时候,调用函数sem_wait()来减少信号量。其实,我们是可以把锁当作一个0-1信号量的。

    它们是在/usr/include/semaphore.h中进行定义的,信号量的数据结构为sem_t, 本质上,它是一个long型整数

    相关函数

    在使用semaphore之前,我们需要先引入头文件#include <semaphore.h>

    • 初始化信号量: int sem_init(sem_t *sem, int pshared, unsigned int value);
      • 成功返回0,失败返回-1
      • 参数
      • sem:指向信号量结构的一个指针
      • pshared: 不是0的时候,该信号量在进程间共享,否则只能为当前进程的所有线程们共享
      • value:信号量的初始值
    • 信号量减1操作,当sem=0的时候该函数会堵塞 int sem_wait(sem_t *sem);
      • 成功返回0,失败返回-1
      • 参数
      • sem:指向信号量的一个指针
    • 信号量加1操作 int sem_post(sem_t *sem);
      • 参数与返回同上
    • 销毁信号量 int sem_destroy(sem_t *sem);
      • 参数与返回同上

    信号量和锁的区别

    信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这 个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”

    也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务 并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进 行操作。在有些情况下两者可以互换。

    两者之间的区别:

    作用域

    信号量: 进程间或线程间(Linux仅线程间的无名信号量pthread semaphore)

    互斥锁: 线程间

    上锁时 

    信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加一,但是sem_wait返回之前还是会将此value值减一

    互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源

    以下是信号灯(量)的一些概念:

    信号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即资 源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;

    没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持 灯亮状态。当然,这样的操作原语也意味着更多的开销。

    信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。

     原子操作

    多进程线程)访问共享资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。原子操作(atomic operation)是不需要synchronized,这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。之所以要把它们排除在外是因为它们都比较大,而JVM的设计规范又没有要求读操作和赋值操作必须是原子操作(JVM可以试着去这么作,但并不保证)。

     

    转载于:https://www.cnblogs.com/ssssdy/p/7133061.html

    展开全文
  • 前言碎语Synchronized和 ReentrantLock 大家应该都不陌生了,作为java中最常用的本地,最初版本中 ReentrantLock 的性能远远强于 Synchronized 的,后续java在一次次的版本迭代中 对 Synchronized 进行了大量的...

    前言碎语

    Synchronized和 ReentrantLock 大家应该都不陌生了,作为java中最常用的本地锁,最初版本中 ReentrantLock 的性能是远远强于 Synchronized 的,后续java在一次次的版本迭代中 对 Synchronized 进行了大量的优化,直到 jdk1.6 之后,两种锁的性能已经相差无几,甚至 Synchronized 的自动释放锁会更好用。

    在面试时被问到 Synchronized 和 ReentrantLock 的使用选择时,很多朋友都脱口而出的说用 Synchronized ,甚至在我面试的时候问面试者,也很少有人能够答出所以然来,moon 想说,这可不一定, 只对标题感兴趣的同学可以直接划到最后 ,我可不是标题党~

    Synchronized使用

    在 java 代码中 synchronized 的使用 是非常简单的

    1.直接贴在方法上(锁住的是当前类的字节码文件)

    2.贴在代码块儿上(锁住的是对象)

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    程序运行期间,Synchronized那一块儿代码发生么什么?

    来看一张图

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    在多线程运行过程中, 线程会去先抢对象的监视器 ,这个监视器是对象独有的,其实就相当于一把钥匙,抢到了,那你就获得了当前代码块儿的执行权。

    其他没有抢到的线程会进入队列(SynchronizedQueue)当中等待,等待当前线程执行完后,释放锁.

    最后当前线程执行完毕后通知出队然后继续重复当前过程.

    从 jvm 的角度来看 monitorenter 和 monitorexit 指令代表着代码的执行与结束 。

    SynchronizedQueue:

    SynchronizedQueue 是一个比较特殊的队列,它没有存储功能,它的功能就是维护一组线程,其中每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。因此此队列内部其 实没有任何一个元素,或者说容量是0,严格说并不是一种容器。由于队列没有容量,因此不能调用 peek 操作,因为只有移除元素时才有元素。

    举个例子:

    喝酒的时候, 先把酒倒入酒盅,然后再倒入酒杯,这就是正常的队列 。

    喝酒的时候, 把酒直接倒入酒杯,这就是 SynchronizedQueue 。

    这个例子应该很清晰易懂了,它的好处就是可以直接传递,省去了一个第三方传递的过程。

    聊聊细节,锁升级的过程

    在 jdk1.6 以前,Synchronized 是一个重量级锁,还是先贴一张图

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    这就是为什么说,Synchronized 是一个重量级锁的原因, 因为每一次锁的资源都是直接和 cpu 去申请的,而 cpu 的锁数量是固定的 ,当 cpu 锁资源使用完后还会进行锁等待,这是一个非常耗时的操作。

    但是在jdk1.6,针对代码层面进行了大量的优化,也就是我们常说的锁升级的过程。

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    这就是一个 锁升级的过程 ,我们简单的说说:

    无锁:对象一开始就是无锁状态。

    偏向锁: 相当于给对象贴了一个标签 (将自己的线程 id 存入对象头中),下次我再进来时,发现标签是我的,我就可以继续使用了。

    自旋锁: 想象一下有一个厕所,里面有一个人在,你很想上但是只有一个坑位,所以你只能徘徊等待,等那个人出来以后,你就可以使用了 。 这个自旋是使用 cas 来保证原子性的,关于 cas 我这里就不再赘述了。

    重量级锁: 直接向 cpu 去申请申请锁 ,其他的线程都进入队列中等待。

    锁升级是什么时候发生的?

    偏向锁:一个线程获取锁时会由无锁升级为偏向锁

    自旋锁:当产生线程竞争时由偏向锁升级为自旋锁,想象一下 while(true) ;

    重量级锁:当线程竞争到达一定数量或超过一定时间时,晋升为重量级锁

    锁的信息是记录在哪里的?

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    这张图是对象头中 markword 的数据结构 ,锁的信息就是在这里存放的,很清楚的表明了锁在升级的时候锁信息的变动, 其实就是通过二进制的数值,来对对象进行一个标记,每个数值代表一种状态 。

    既然synchronized有锁升级那么有锁降级吗?

    这个问题和我们的题目就有很大的关联了。

    在 HotSpot 虚拟机中是有锁降级的, 但是仅仅只发生在 STW 的时候 ,只有垃圾回收线程能够观测到它,也就是说, 在我们正常使用的过程中是不会发生锁降级的,只有在 GC 的时候才会降级。

    所以题目的答案,你懂了吗?哈哈,我们接着往下走。

    ReentrantLock的使用

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    ReentrantLock 的使用也是非常简单的,与 Synchronized 的不同就是需要自己去手动释放锁,为了保证一定释放,所以通常都是和 try~finally 配合使用的。

    ReentrantLock的原理

    ReentrantLock 意为 可重入锁 ,说起 ReentrantLock 就不得不说 AQS ,因为其 底层就是使用 AQS 去实现的。

    ReentrantLock有 两种模式 ,一种是公平锁,一种是非公平锁。

    公平模式下等待线程入队列后会严格按照队列顺序去执行

    非公平模式下等待线程入队列后有可能会出现插队情况

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    这就是ReentrantLock的结构图,我们看这张图其实是很简单的,因为主要的实现都交给AQS去做了,我们下面着重聊一下AQS。

    AQS

    AQS(AbstractQueuedSynchronizer): AQS 可以理解为就是 一个可以实现锁的框架 。

    简单的流程理解:

    公平锁:

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    第一步:获取状态的 state 的值。如果 state=0 即代表锁没有被其它线程占用,执行第二步。如果 state!=0 则代表锁正在被其它线程占用,执行第三步。

    第二步:判断队列中是否有线程在排队等待。如果不存在则直接将锁的所有者设置成当前线程,且更新状态 state 。如果存在就入队。

    第三步:判断锁的所有者是不是当前线程。如果是则更新状态 state 的值。如果不是,线程进入队列排队等待。

    非公平锁:

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    第一步:获取状态的 state 的值。如果 state=0 即代表锁没有被其它线程占用,则设置当前锁的持有者为当前线程,该操作用 CAS 完成。如果不为0或者设置失败,代表锁被占用进行下一步。

    此时获取 state 的值,如果是0,代表刚好线程释放了锁,此时将锁的持有者设为自己如果不是0,则查看线程持有者是不是自己如果是,则给state+1,获取锁如果不是,则进入队列等待

    读完以上的部分相信你对AQS已经有了一个比较清楚的概念了,所以我们来聊聊小细节。

    AQS使用state同步状态(0代表无锁,1代表有),并暴露出 getState 、 setState 以及 compareAndSet 操作来读取和更新这个状态,使得仅当同步状态拥有一个期望值的时候,才会被原子地设置成新值。

    当有线程获取锁失败后,AQS是通过一个 双向的同步队列 来完成同步状态的管理,就被添加到队列末尾。

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    这是定义头尾节点的代码,我们可以先使用 volatile 去修饰的,就是保证让其他线程可见, AQS 实际上就是修改头尾两个节点来完成入队和出队操作的。

    AQS 在锁的获取时,并不一定只有一个线程才能持有这个锁,所以此时有了 独占模式和共享模式 的区别,我们本篇文章中的 ReentrantLock 使用的就是独占模式,在多线程的情况下只会有一个线程获取锁。

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    独占模式的流程是比较简单的,就根据state是否为0来判断是否有线程已经获得了锁,没有就阻塞,有就继续执行后续代码逻辑。

    312f6d176fb3

    动态高并发时为什么推荐重入锁而不是Synchronized?

    共享模式的流程根据state是否大于0来判断是否有线程已经获得了锁,如果不大于0,就阻塞,如果大于0,通过CAS的原子操作来自减state的值,然后继续执行后续代码逻辑。

    ReentrantLock和Synchronized的区别

    其实ReentrantLock和Synchronized 最核心的区别就在于 Synchronized适合于并发竞争低的情况,因为Synchronized的锁升级如果最终升级为重量级锁在使用的过程中是没有办法消除的,意味着每次都要和cpu去请求锁资源,而ReentrantLock主要是提供了阻塞的能力, 通过在高并发下线程的挂起,来减少竞争,提高并发能力 ,所以我们文章标题的答案,也就显而易见了。

    synchronized是一个关键字,是由 jvm层面 去实现的,而ReentrantLock是由 java api 去实现的。

    synchronized是隐式锁,可以 自动释放锁 ,ReentrantLock是显式锁,需要 手动释放锁 。

    ReentrantLock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去, 不能够响应中断。

    ReentrantLock可以获取锁状态,而synchronized不能。

    说说标题的答案

    其实题目的答案就在上一栏目的第一条,也是核心的区别,synchronized升级为重量级锁后无法在正常情况下完成降级,而ReentrantLock是通过阻塞来提高性能的,在设计模式上就体现出了对多线程情况的支持。

    展开全文
  • 张大胖所在的公司这几年发展得相当不错,业务激增,人员也迅速扩展,转眼之间,张大胖已经成为公司的“资深”员工了,更重要的,经过这些年的不懈努力,他终于坐上了架构师的宝座。 但是大胖很快发现,这架构师真...

    张大胖所在的公司这几年发展得相当不错,业务激增,人员也迅速扩展,转眼之间,张大胖已经成为公司的“资深”员工了,更重要的是,经过这些年的不懈努力,他终于坐上了架构师的宝座。

    但是大胖很快发现,这架构师真不是好当的,技术选型、架构设计,尤其是大家搞不定的技术难点,最终都得自己扛起来。沟通、说服、妥协、甚至争吵都是家常便饭,比自己之前单纯做开发的时候难多了。

    公司的IT系统早已经从单机转向了分布式,分布式系统带来了巨大的挑战。这周一刚上班,张大胖的邮箱里已经塞满了紧急邮件。

    1、小梁的邮件

    小梁的邮件里说了一个RPC调用的问题,本来公司的架构组开发了一个RPC框架让各个组去使用,但是各开发小组纷纷抱怨:这个RPC框架不支持动态的服务注册和发现。
    在这里插入图片描述
    张大胖一看这个图就明白怎么回事了,为了支持高并发,OrderService被部署了4份,每个客户端都保存了一份服务提供者的列表,但是这个列表是静态的(在配置文件中写死的),如果服务的提供者发生了变化,例如有些机器down了,或者又新增了OrderService的实例,客户端根本不知道,可能还在傻乎乎地尝试那些已经坏掉的实例呢!

    想要得到最新的服务提供者的URL列表,必须得手工更新配置文件才行,确实很不方便。

    对于这样的问题,大胖马上就意识到,这就是客户端和服务提供者的紧耦合啊。

    想解除这个耦合,非得增加一个中间层不可!

    张大胖想到,应该有个注册中心,首先给这些服务命名(例如orderService),其次那些OrderService 都可以在这里注册一下,客户端就到这里来查询,只需要给出名称orderService,注册中心就可以给出一个可以使用的url, 再也不怕服务提供者的动态增减了。
    在这里插入图片描述
    不知道是不是下意识的行为,张大胖把这个注册中心的数据结构设计成为了一个树形结构:
    在这里插入图片描述
    /orderService 表达了一个服务的概念, 下面的每个节点表示了一个服务的实例。 例如/orderService/node2表示的order service 的第二个实例, 每个节点上可以记录下该实例的url , 这样就可以查询了。

    当然这个注册中心必须得能和各个服务实例通信,如果某个服务实例不幸down掉了,那它在树结构中对于的节点也必须删除, 这样客户端就查询不到了。
    在这里插入图片描述
    嗯,可以在注册中心和各个服务实例直接建立Session, 让各个服务实例定期地发送心跳,如果过了特定时间收不到心跳,就认为这个服务实例挂掉了,Session 过期, 把它从树形结构中删除。

    张大胖把自己的想法回复了小梁,接着看小王的邮件。

    2、小王的Master选举

    小王邮件中说的是三个Batch Job的协调问题,这三个Batch Job 部署在三台机器上,但是这三个Batch Job同一个时刻只能有一个运行,如果其中某个不幸down掉,剩下的两个就需要做个选举,选出来的那个Batch Job 需要“继承遗志”,继续工作。
    在这里插入图片描述
    其实这就是一个Master的选举问题,张大胖一眼就看出了本质。

    只是为了选举出Master, 这三个Batch Job 需要互通有无,互相协调才行,这就麻烦了!

    要不弄个数据库表? 利用数据库表主键不能冲突的特性,让这三个Batch Job 都向同一个表中插入同样的数据,谁先成功谁就是Master !

    可是如果抢到Master的那个Batch Job挂掉了,别人永远就抢不到了! 因为记录已经存在了, 别的Batch Job 没法插入数据了!

    嗯,还得加上定期更新的机制,如果一段时间内没有更新就认为Master死掉了,别的Batch Job可以继续抢… 不过这么做好麻烦!

    换个思路,让他们也去一个注册中心去大吼一声:“我是master!”, 谁的声音大谁是Master 。

    其实不是吼一声,三个Batch Job启动以后,都去注册中心争抢着去创建一个树的节点(例如/master ),谁创建成功谁就是Master (当然注册中心必须保证只能创建成功一次,其他请求就失败了),其他两个Batch Job就对这个节点虎视眈眈地监控,如果这个节点被删除,就开始新一轮争抢,去创建那个/master节点。
    在这里插入图片描述
    什么时候节点会被删除呢? 对,就是当前Master的机器down掉了 ! 很明显,注册中心也需要和各个机器通信,看看他们是否活着。
    在这里插入图片描述
    等等,这里还有一个复杂的情况, 如果机器1并没有死掉,只是和注册中心长时间连接不上,注册中心会发现Session超时,会把机器1创建的/master删除。 让机器2和机器3去抢,如果机器3成为了master, 开始运行Batch Job, 但是机器1并不知道自己被解除了Master的职务, 还在努力的运行Batch Job,这就冲突了!

    看来机器1必须得能感知到和注册中心的连接断开了,需要停止Batch Job才行,等到和注册中心再次连接上以后,才知道自己已经不是master了,老老实实地等下一次机会吧。

    无论哪种方案,实现起来都很麻烦,这该死的分布式!

    先把思路给小王回复一下吧。接着看小蔡的邮件。

    3、小蔡的分布式锁

    小蔡的邮件里说的问题更加麻烦,有多个不同的系统(当然是分布在不同的机器上!),要对同一个资源操作。
    在这里插入图片描述
    这要是在一个机器上,使用某个语言内置的锁就可以搞定,例如Java的synchronized , 但是现在是分布式啊,程序都跑在不同机器的不同进程中, synchcronized一点用都没有了!

    这是个分布式锁的问题啊!

    能不能考虑下Master选举问题中的方式,让大家去抢? 谁能抢先在注册中心创建一个/distribute_lock的节点就表示抢到这个锁了,然后读写资源,读写完以后就把/distribute_lock节点删除,大家再来抢。

    可是这样的话某个系统可能会多次抢到,不太公平。

    如果让这些系统在注册中心的/distribute_lock下都创建子节点, 然后给每个系统一个编号,会是这个样子:
    在这里插入图片描述
    然后各个系统去检查自己的编号,谁的编号小就认为谁持有了锁, 例如系统1。

    系统1持有了锁,就可以对共享资源进行操作了, 操作完成以后process_01这个节点删除, 再创建一个新的节点(编号变成process_04了):
    在这里插入图片描述
    其他系统一看,编号为01的删除了,再看看谁是最小的吧,是process_02,那就认为系统2持有了锁,可以对共享资源操作了。 操作完成以后也要把process_02节点删除,创建新的节点。这时候process_03就是最小的了,可以持有锁了。
    在这里插入图片描述
    这样循环往复下去… 分布式锁就可以实现了!

    看看,我设计的这个集中式的树形结构很不错吧,能解决各种各样的问题! 张大胖不由得意起来。

    好,先把这个想法告诉小蔡,实现细节下午开个会讨论。

    4、Zookeeper

    正准备回复小蔡的时候,大胖突然意识到,自己漏了一个重要的点,那就是注册中心的高可用性,如果注册中心只有那么一台机器,一旦挂掉,整个系统就玩完了。

    这个注册中心也得有多台机器来保证高可用性,那个自己颇为得意的树形结构也需要在多个机器之间同步啊,要是有机器挂掉怎么办? 通信超时怎么办? 树形结构的数据怎么在各个机器之间保证强一致性?

    小王、小梁、小蔡的原始问题没有解决,单单是这个注册中心就要了命了。 以自己公司的技术实力,搞出一套这样的注册中心简直是Mission Impossible !

    大胖赶紧上网搜索,看看有没有类似的解决方案,让大胖感到万分幸运的是,果然有一个,叫做Zookeeper

    Zookeeper 所使用的树形结构和自己想象的非常类似,更重要的是,人家实现了树形结构数据在多台机器之间的可靠复制,达到了数据在多台机器之间的一致性。并且这多台机器中如果有部分挂掉了/或者由于网络原因无法连接上了, 整个系统还可以工作。

    大胖赶快去看Zookeeper的关键概念和API:

    1. Session : 表示某个客户系统(例如Batch Job)和ZooKeeper之间的连接会话, Batch Job连上ZooKeeper以后会周期性地发送心跳信息, 如果Zookeepr在特定时间内收不到心跳,就会认为这个Batch Job已经死掉了, Session 就会结束。

    2. znode : 树形结构中的每个节点叫做znode, 按类型可以分为永久的znode(除非主动删除,否则一直存在),临时的znode(Session结束就会删除)和 顺序znode(就是小蔡的分布式锁中的process_01,process_02…)。

    3. Watch : 某个客户系统(例如Batch Job)可以监控znode, znode节点的变化(删除,修改数据等)都可以通知Batch Job, 这样Batch Job可以采取相应的动作,例如争抢着去创建节点。

    嗯,这些概念和接口应该可以满足我们的要求了, 就是它了,下午召集大家开会开始学习Zookeeper吧。

    后记:本文从使用者的角度描述了Zookeeper有什么用处,至于它内部是如何工作,那是另外一个Big topic了,我们以后再讲。

    展开全文
  • 动态密码

    2015-04-12 16:20:00
     要想知道什么样的网银系统安全的,首先要知道哪些网银系统不安全的。  我的观点,所有不带有身份认证令牌硬件设备的网银系统都不安全的。  这些系统包括各种“大众版”网银,以及一些所谓的数字证书...
  • 很多小伙伴都会有这样的经历,出门之后没走多远,却已然忘记是否锁门,有强迫症的人就会重新返回查看,以...Win10动态锁屏功能有什么用平常我们锁屏通过“WIN+L”的快捷键实现的,或者通过更改电源的设置,让其...
  • 先来了解下什么是一个有效的安卓解锁手势: 每一个解锁手势必须至少经过 m 个点、最多经过 n 个点。 解锁手势里不能设置经过重复的点。 假如手势中有两个点顺序经过的,那么这两个点的手势轨迹之间绝对不能跨过...
  • (梵高,收获的景象)共享单车的发展过程中,其中的一个争论焦点车锁。对企业来说,无时不在动态发展中。共享单车的车锁也不断的螺旋式上升。ofo于今年7月份率先开始把全新智...
  • 动态分区是什么?我们这里暂不并不做科普,但由于动态分区的出现,system等分区变的更加严格,无论你改什么,都会导致手机验证检测失败,直接不能打开启动。不过庆幸的是有互联网大神研究实现了解锁system分区,目前...
  • 动态分区,一个对于新手比较陌生的词汇,这也目前出厂安卓10手机,主要采用的分区模式,至于动态分区给我们带来哪些好处,这里不做探究,仅仅大家怎么判断自己的手机是否使用动态分区第一步:查看自己的手机出厂...
  •  要想知道什么样的网银系统安全的,首先要知道哪些网银系统不安全的。 我的观点,所有不带有身份认证令牌硬件设备的网银系统都不安全的。 这些系统包括各种“大众版”网银,以及一些所谓的数字证书“专业...
  • 前言碎语Synchronized和 ReentrantLock 大家应该都不陌生了,作为java中最常用的本地,最初版本中 ReentrantLock 的性能远远强于 Synchronized 的,后续java在一次次的版本迭代中 对 Synchronized 进行了大量的...
  • 欢迎来到"奇点微学堂",我尹仲会,帮你自我升级,每天更新一个知识点,解锁更多技能,提升自身价值。前面有一篇文章《这样使用photoshop软件给衣服添加图案,整个人都亮了》讲了给衣服添加图案,不了解的朋友可以...
  • 生活就像一盒巧克力,你永远不知道你的下一块口味是什么。——《阿甘正传》巧克力,无法拒绝的味道它,有点微微苦涩,但又甘甜开心或悲伤,顺心或迷惑,勇敢或怯懦总想来一场“甜蜜暴击”,庆祝或慰藉一番那么,它...
  • 什么是Zookeeper Zookeeper一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group ...
  • 从Flink 1.5发布公告中,我们知道Flink现在支持“广播状态”,...另外我不知道在有或没有广播状态的Flink CEP实现“动态模式”时有什么区别?我将不胜感激如果有人可以用代码举例解释差异 .=============Updating...
  • 文章目录引言什么是锁顺序死锁动态顺序死锁实践启示录 引言 初次接触死锁的概念大学的一门课程《操作系统原理》中描述的“哲学家进餐”问题。操作系统中,由于各个进程共享系统资源而可能出现死锁问题。同样 ...
  • Synchronized 和 ReentrantLock 大家应该都不陌生了,作为java中最常用的本地,最初版本中 ReentrantLock 的性能远远强于 Synchronized 的,后续java在一次次的版本迭代中 对 Synchronized 进行了大量的优化,...
  • 什么是锁优化? JDK1.5为了解决synchronize效率问题,出现了Lock。(Lock源码分析看这篇文章Java8 Lock详解(AQS,CAS))JDK1.6对synchronize进行了大量优化,让synchronize能够和和Lock性能不相上下。 ...
  • 在上学的时候,老师讲到进程与线程的时候可能这样讲的:进程一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,操作系统进行资源分配和调度的一个独立单元,应用程序运行的载体。线程程序...
  • 进程与线程什么是进程?程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序指令的集合,它进程运行的静态描述文本;进程...
  • 什么是句柄C#

    2019-08-12 19:10:59
    什么是句柄C# 话不多说,下面分享下我对句柄的看法。 如果没有意外的话,ABCDE他们将依次进行占用CPU资源。但是可能会发生如下情况 句柄:就是用来维护进程或者系统范围内的一个标识。 就比如我们去访问一个...
  • 大家应该都不陌生了,作为java中最常用的本地,最初版本中 ReentrantLock 的性能远远强于 Synchronized 的,后续java在一次次的版本迭代中 对 Synchronized 进行了大量的优化,直到 jdk1.6 之后,两种的性能...
  • boost 互斥体和

    2019-08-11 21:42:41
    1、共享资源一个自动住的房间,互斥体钥匙,进入房间必须取钥匙,离开房间应该还钥匙。这就对应着互斥体的lock(取钥匙)和unlock(还钥匙)。 2、考虑下面的场景:还钥匙的时候出现异常,会发生什么?  ...
  • MarkdownPad Document进程与线程程序并不能单独...进程程序的一次执行活动,属于动态概念。进程一般由程序、数据集、进程控制块三部分组成。有了进程为什么还要有线程呢?因为进程还是有缺陷的:进程只能在一个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 511
精华内容 204
关键字:

动态锁是什么