synchronized
底层实现原理
synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但在 Java 6 的时候,Java 虚拟机 对此进行了大刀阔斧地改进,提供了三种不同的 monitor 实现,也就是常说的三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。
ps: 用户态可以理解为用户能直接操作的 APP,以前用户可以直接通过用户态访问硬件,操作 内存、网卡、显示器等,这样是很消耗性能的,很容易让机器造成死机,现在相当于是加入了内核态(kernel),用户现在要去访问硬盘呀或者是做一些危险的操作的时候,就会通过内核态去访问。
synchronized 锁升级原理
在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象;如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象(CAS),此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。(早期属于重量级锁,是需要经过操作系统允许,后来改进后大多数情况是不需要了) 概括: 偏向锁->自旋锁(CAS)->重量级锁
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
volatile(重点)
当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。
_volatile 修饰之后并不是让线程直接从主内存中获取数据,依然需要将变量拷贝到工作内存中_。
作用
- 多线程主要围绕可见性和原子性两个特性而展开,使用 volatile 关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到 volatile 变量,一定是最新的数据,但是它不能保证原子性。
为什么不能保证原子性呢?
首先需要了解的是,Java 中只有对基本类型变量的赋值和读取是原子操作,如 i = 1 的赋值操作,但是像 j = i 或者 i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取 i 的值,再将 i 的值赋值给 j,两个原子操作加起来就不是原子操作了。
所以,如果一个变量被 volatile 修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性了。
举个栗子
一个变量 i 被 volatile 修饰,两个线程想对这个变量修改,都对其进行自增操作也就是 i++,i++的过程可以分为三步,首先获取 i 的值,其次对 i 的值进行加 1,最后将得到的新值写会到缓存中。
线程 A 首先得到了 i 的初始值 100,但是还没来得及修改,就阻塞了,这时线程 B 开始了,它也得到了 i 的值,由于 i 的值未被修改,即使是被 volatile 修饰,主存的变量还没变化,那么线程 B 得到的值也是 100,之后对其进行加 1 操作,得到 101 后,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。
问题来了,线程 A 已经读取到了 i 的值为 100,也就是说读取的这个原子操作已经结束了,所以这个可见性来的有点晚,线程 A 阻塞结束后,继续将 100 这个值加 1,得到 101,再将值写到缓存,最后刷入主存,所以即便是 volatile 具有可见性,也不能保证对它修饰的变量具有原子性。
原文链接:https://blog.csdn.net/xdzhouxin/article/details/81236356
如何防止死锁
1、尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLockReentrantReadWriteLock),
2、设置超时时间,超时可以退出防止死锁。
3、尽量使用 Java. util. concurrent 并发类代替自己手写锁。
4、尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
5、尽量减少同步的代码块。
synchronized 和 volatile 的区别
- volatile 修饰变量;synchronized 修饰方法、代码段、类
- volatile 仅能实现变量的修改可见性,不能保证原子性;synchronized 都可以;
- volatile 不会造成线程的阻塞;synchronized 有可能造成阻塞;
- volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化;
- volatile 可以防止指令重排。(volatile 关键字通过“内存屏障”来防止指令被重排序。)
synchronized 和 Lock 有什么区别?
- synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
- synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
synchronized 和 ReentrantLock 区别是什么?
synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。
主要区别如下:
- ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
- ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
- ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。