公平锁就是按照先来先服务、非公平就是不管你什么时候来,唤醒的时候都是随即唤醒。例如synchronize就是非公平锁,ReentrantLock既可以作为公平锁,也可以作为非公平锁。
可重入锁可重入锁就是说某个线程已经获取到某个锁,这个线程跨域在次获取到这个锁而不会出现死锁。synchronize和ReentrantLock都是可重入锁。需要注意的是ReentrantLock锁需要手动释放锁、并且加锁次数要和释放次数一致。
好处:同一个线程访问这个对象就不需要等上一把锁释放我才能访问,减少代码复杂度;
//就比如我想要在第一个方法里面执行第二个方法,由于两个方法都加了锁,如果没有可重入锁,这个程序是允许不了的。
独享锁和共享锁
独享锁是指该锁一次只能被一个线程持有。共享锁指该锁可以由多个线程持有。synchronized和ReentrantLock都是独享锁。但是对于Lock的另一个实现类ReadWriteLock
,其读锁是共享锁,其写锁是独享锁。
读锁可以保证并发读操作非常高效。独享锁与共享锁也是通过AQS来实现的。
独占锁:
独占锁是只有头节点获取锁,其余节点的线程继续等待,等待锁被释放后,才会唤醒下一个节点的线程。独占锁的同步状态state值在0和1之间切换,保证同一时间只能有一个线程是处于活动的,其他线程都被阻塞。 共享锁:
共享锁是只要头节点获取锁成功,就在唤醒自身节点对应的线程的同时,继续唤醒AQS队列中的下一个节点的线程,每个节点在唤醒自身的同时还会唤醒下一个节点对应的线程,以实现共享状态的“向后传播”,从而实现共享功能。共享锁的同步状态state值在整数区间内(自定义实现),如果state值<0则阻塞,否则不阻塞。
乐观锁和悲观锁
乐观锁不会去上锁,只是在更新的时候判断一下是否修改了数据;如果别人修改了数据则放弃操作,否则执行。
悲观锁在操作数据的时候直接把数据锁住,知道操作完后才会释放锁。synchronization关键字加锁、cas机制和版本号机制。cas只能对单个变量操作实现原子性,涉及到多个变量可以有synchronization来加锁。
自旋锁
自旋锁与互斥锁比较类似,都是为了解决对某项资源的互斥使用。
场景:互斥锁的优点是在没有获取到锁,线程会及时让出cpu,从而提高cpu利用率。但缺点是短时间内有大量线程的加锁/解锁,则频繁的唤醒/阻塞会导致大量线程上下文切换而降低系统性能。因此互斥锁适用于线程会在较长时间内持有锁的场景。自旋锁如果长时间都无法获取锁,则会造成cpu长时间空转,造成cpu资源极大浪费。所以适用于在获取锁后短时间内释放锁的场景
实现:
-
原始自旋锁:private final AtomicReference<Thread> lockOwner = new AtomicReference<>();//原子引用类型。只有当这个引用为空的时候你才能去获取锁然后将当前线程cas到引用中。
-
票锁:(TicketLock)票锁在加锁的时当前线程会预先原子性的拿到一个逐步递增且唯一的排队服务号,只有当前票锁的服务号票号和自己拿到的排队服务号一致时才认为加锁成功。而解锁时则将当前票锁的服务票号递增。
这两个的缺点:每一个线程都会不断的访问一个变量值,在多核CPU架构下性能会受到一定的影响
-
CLH锁:决多核cpu体系中全部加锁线程都访问同一内存地址而出现过多内存竞争