聊聊Java的锁

150 阅读4分钟

先说一下什么是锁? 锁是一种机制,可以用来防止多个线程同时访问共享资源,从而保证线程的安全性,此外,锁还具有原子性的特点,锁能保证任何复合操作的原子性,被锁住的对象,哪怕被指令重排序了,也能保证其原子性 锁有很多实现方式,最常见的有: 乐观锁/悲观锁;公平锁/非公平锁;可重入锁/不可重入锁;独占锁/共享锁;互斥锁/读写锁;自旋锁等等 Java的锁基本就把上面的锁都实现了,比如synchronized、volatile、ReentrantLock、ReentrantReadWriteLock这些,除了synchronized和volatile,其它实现锁的方式基本上都是实现Lock接口然后用静态内部类去重写了AQS里的方法,接下来就来浅谈一下 对于Synchronized,它是悲观锁/非公平锁/可重入锁/独占锁/互斥锁,它具有这么几个特性:有序性、可见性、原子性、可重入性,它能够修饰普通方法、静态方法、同步代码块,它还有几种锁的状态,比如无锁、偏向锁、轻量级锁、重量级锁,加锁后默认偏向锁,然后根据情况,将锁里的markword标志位进行升级或者撤销 对于volatile,它是一种轻量级别的锁(轻量级别的synchronized),它可以保证可见性,有序性以及单个操作的原子性(复合操作如++这种是不保证的),它也是一个关键字,如果用volatile修饰变量使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度,volatile在读多写少的情况下比使用锁更加方便,比如将某个字段声明称volatile,Java线程内存模型就能确保所有线程看到这个变量的值是一致的,它在内部禁止了指令重排序,至于为什么要禁止,就跟JMM内存模型有关了,Java的JMM内存模型允许编译器或者处理器对程序代码进行指令重排序来提高性能,在单线程情况下,指令重排序可以保证程序最终的执行结果与程序顺序执行的结果完全一致,但是在多线程情况下就会存在问题,它保证不了结果的一致性,所以volatile出于这一层的考虑,就加入了内存屏障来禁止指令重排序,而内存屏障也整是volatile用来保证有序性和可见性的措施 对于ReentrantLock,它是非公平锁/可重入锁/独占锁/互斥锁,它默认是非公平锁,因为非公平锁的效率比公平锁要高,至于可重入的特性就是说,任意的线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,ReentrantLock操作锁是显示的,需要自己去加锁和解锁,在源码层面,它就是实现了Lock接口里的各个方法以及用静态内部类重写了AQS的部分方法并对其做了一些扩充,里面用到了模板方法设计模式(自己如果想实现锁也可以参照一下ReentrantLock里的源码) ReentrantReadWriteLock,它的读锁是共享锁,写锁是独占锁,默认也是非公平锁,它实现了ReadWriteLock接口的方法,其静态内部类也继承并重写了AQS里的方法,需要值得一提的是ReentrantReadWriteLock的state高16位代表读锁状态,低16位代表写锁状态,其它的看下源码也就懂了 对于自旋锁,当一个线程在获取锁的时候,如果锁已经被其它线程获取到了,那么这个线程就要进行循环等待,然后它会不断去判断是否能够被成功获取,直到获取到锁了才会退出循环,如果自旋次数过多,也是会消耗CPU的资源的,像CAS就用到了自旋锁,除了自旋锁之外还有乐观锁以及比较并交换,用CAS也挺简单的,就设置一个预期值也要更新的值就行,常见的自旋锁的话有:Ticket Lock (排队自旋锁)、和两种基于链表的自旋锁MCS Lock以及CLH Lock,这些感兴趣的可以自行了解 这些大概就是我对于Java中锁的一个理解了,如果有偏差,敬请指出 OK,我话说完