对于synchronized的总结

1. synchronized的特性

对于synchronized来说:

1. 乐观锁/悲观锁 自适应

2. 轻量级锁/重量级锁 自适应

3. 自旋锁/挂起等待锁 自适应

4. 不是读写锁

5. 非公平锁

6. 可重入锁

1.1 乐观锁和悲伤锁

两种锁不同的锁的实现方式

  1. 乐观锁,在加锁之前,预估当前出现锁冲突的概率不大,因此在加锁的时候就不会做太多的工作。加锁过程做的事情比较少,加锁的速度可能就更快,但是引入一些其他的问题(但是可能会消耗更多的CPU资源)。

  2. 悲观锁,在加锁之前,预估当前锁冲突出现的概率比较大,因此加锁的时候,就会做更多的工作。做的事情更多,加锁的速度可能更慢,但是整个过程中不容易出现其他问题。

  注意:这里是预估的时候做出的判断。

1.2 轻量级锁和重量级锁

  1.轻量级锁,加锁的开销小,加锁的速度更快。=> 轻量级锁,一般就是乐观锁。

  2.重量级锁,加锁的开销大,加锁的速度更慢。=> 重量级锁,一般也就是悲观锁。

   注意: 轻量重量,加锁之后,对结果的评价。而悲观乐观,是加锁之前,对外发生的事情进行的预估。整体来说,这两种角度,描述的是同一个事情。

1.3 自旋锁和挂起等待锁

  1. 自旋锁 就是轻量级锁的一种典型实现。进行加锁的时候,搭配一个while循环,如果加锁成功,循环就结束了。如果加锁不成功,不是阻塞放弃CPU,而是进行下一次循环,再次尝试获取取到锁。而这个操作是反复快速执行的,就成为“自旋”,一旦其他线程释放了锁,就能第一时间拿到锁,当然自旋锁也是乐观锁,使用自旋的前提 ,就是预期锁冲突概率不大,其他线程释放了锁,就能第一时间拿到。就会出现了这种情况,当前加锁的线程特别多,自旋意义就不大了,白白浪费CPU了。

  2. 挂起等待锁 就是重量级锁的一种典型实现。进行挂起等待的时候,就需要内核调度器介入了,这一块要完成的操作太多了,真正获取到锁要花的时间就更多一些了。同时也是一种悲观锁,这个锁适用于锁冲突激烈的情况。

对于synchronized的总结_第1张图片 

  这里给大家举个列栗子,哥么是一个老舔狗了,有一天我像女神(有男朋友)表白:女神给了我一句滚!!我是不是有俩种选择 1.  放弃了,等女神分手了我再来,这段时间哥么猛猛搞学生,不关系女神的事情了。 2. 我明白只要我舔的好,没有墙角挖不到。 像1情况 我的线程进行了阻塞,我就把CPU让出来就可以安心学习了,某一天女神分手了(我可能知道的时候已经几个月了,因为线程一旦进入阻塞,就需要重新参加系统调度什么时候能调度上为止)我就对女神又尝试加锁。像2情况 我每天都得花时间和女神进行交流(导致我没心思干别的事情),所以女神一分手我的机会就来了,就有很大的可能性乘虚而入,一举加锁成功!!!

1.4 普通互斥锁和读写锁

  1. 普通互斥锁类似于synchronized,操作设计到加锁和解锁。

  2. 这里的读写锁分为两种情况 1. 加读锁 2. 加写锁

   读锁和读锁之间,不会出现锁冲突(不会阻塞)

   写锁和写锁之间,会出现锁冲突(会阻塞)

   读锁和写锁之间,会出现锁冲突(会阻塞)

因此我们的总结出来: 1. 一个线程加读锁的时候,另一个线程,只能读,不能写。2. 一个线程加写锁的时候,另一个线程,不能写,也不能读。

那为什么要引入读写锁??? 

  如果俩个线程度,本身就是线程安全的,不需要互斥,如果使用synchronized 这种方式加锁,俩个线程读,也会产生互斥,产生阻塞(对于性能有一定的损失)。完全给读操作不加锁也不行,就怕一个线程读一个线程写,可能会读到写了一半的数据。读写锁就可以解决这个问题(synchronized不是读写锁)。

1.5 公平锁 和 非公平锁

对于synchronized的总结_第2张图片 

  这里的“公平”,遵循先来后到,才叫公平!!!这个也最早发明公平锁的大佬定义的。

  1.公平锁 如果要实现公平锁,就需要引入额外的数据结构(引入队列,记录每个线程先后顺序),才能实现公平,使用了公平锁,天然就可以避免线程饿死的问题。

  2. 非公平锁 站在系统原生的锁的角度,就是“非公平锁”,系统线程的调度本身就是无序的随机的,上一个线程释放锁了之后,接下来唤醒哪个线程,不好说。 

1.6  可重入锁和不可重入锁

  这里咋么简单的解释一下,一个线程针对这一把锁,连续加锁两次,不会死锁,就是可重入锁,会死锁,则就是不可重入锁。而在synchronized时可重入锁,系统自带的锁,是不可重入锁,可重入锁中需要记录持有锁的线程是谁,加锁的次数的计数。

2. synchronized的加锁过程 

 2.1 偏向锁阶段

  核心思想,“懒汉模式”,能不加锁,就不加锁,能晚加锁,就晚加锁,所谓的偏向锁,并非真的加锁了,而是做一个非常轻量的标记。

  也可以为搞暧昧,就是偏向锁,只是做了一个标记没有真正加锁,一旦有其他线程来和我竞争这个锁,就在另一个线程之前先把锁获取到。从偏向锁就会升级到轻量级锁(真加锁,就会有互斥了),如果我搞暧昧的过程中,要是没有人来和我竞争,整个过程我就把加锁操作完全省略了。可以理解为非必要不加锁

2.2 轻量级锁阶段

  这里是(有竞争,但是不多)此处就是通过自旋锁的方式来实现,优势 : 另外的线程把锁释放了,就会第一时间拿到锁。 劣势: 比较消耗CPU 。于此同时,synchronized内部也会统计当前这个锁对象上,有多少个线程在参与竞争,这里发现参与竞争的线程多了,就会进一步升级到重量级锁。

2.3 重量级锁阶段 

此时拿不到锁的线程就不会继续自旋了,而是进入“阻塞等待”,就会让出CPU(不会使CPU占用率太高),当前线程释放锁的时候,就由系统随机唤醒一个线程来获取锁了。

注意:此处锁只能升级,不能降级。 

2.4 锁消除

  也是synchronized中内置的优化策略,编译器优化的一种方式,编译器编译代码的时候如果发现这个代码,不需要加锁,就会自动把锁给干掉。(模棱俩可的时候编译器也不知道这里是不是要加,都不会去消除)。 

2.5 锁粗化

  会把多个细粒度的锁,合并一个粗粒度的锁。细粒度指的是synchronized{}大括号里包含的代码越少,就认为锁的粒度越细。

  通常情况下,是更偏好与让锁的细度细一些,更有利于多个线程并发执行,但是有的时候是希望锁的的粒度粗点也挺好!!!

总结: 

  synchronized 背后设计到了很多的“优化手段”。

  1. 锁升级:偏向锁->轻量级锁->重量级锁

  2. 锁消除:自动干掉不必要的锁。

  3. 锁粗化: 把多个细粒度的锁合并成一个粗细粒度的锁,减少了锁竞争的开销。

你可能感兴趣的:(java-ee)