java中的锁有很多种类,常见的有公平锁和非公平锁、乐观锁和悲观锁、可重入和不可重入锁等。
下面做了一个分类:
要不要锁住同步资源-乐观锁和悲观锁
乐观锁:认为在修改共享数据的时候, 其他线程不会修改此共享数据,只是在提交的时候会将内存值和期望值进行比较,如果相同就会更新,如果不同就会重试。乐观锁常用的算法是CAS(比较并交换),常用在读多写少的情况。
乐观锁有什么缺点?
1.ABA问题 2.自旋时间过长会浪费cpu资源。
悲观锁:认为在修改共享数据的时候,其他线程一定会修改此共享数据。所以会对修改过程进行加锁处理,保证修改到提交只有一个线程在执行。悲观锁常用的锁,synchronized和lock。
悲观锁有什么缺点?
一个线程获得了锁,其他线程就会阻塞。在唤醒和阻塞的时候,需要切换核态,同时要恢复现场,比较耗费处理器的时间,切换核态以及恢复现场所用的时间很可能比执行加锁代码块的时间长。
自旋锁和适应性自旋锁
自旋锁的实现原理也是CAS。他可以让线程自旋等待,而不用阻塞,造成切换cpu状态。但是自旋时间过长会浪费cpu资源。
适应性自旋锁:可以解决自旋锁的自旋时间过长。每次自旋的时间不固定,由上一次的自旋时间决定。如果某个锁,自旋很少获得,那么以后尝试获取这个锁就省掉自旋过程,直接阻塞线程。
公平锁和非公平锁
公平锁:会按照申请锁的顺序,依次让同步队列中的第一个线程获得锁。每一个线程都会按照先进先出的方式获得锁,缺点就是吞吐率低。
非公平锁:可以不按照申请锁的顺序,刚进来的线程和同步队列的第一个线程一起竞争锁(不考虑排队问题),那个刚进来的线程可以不阻塞的获取锁。吞吐率高,但是可能某些线程会被饿死。
参考公平锁和非公平锁的源码:
通过上图中的源代码对比,我们可以明显的看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()
再进入hasQueuedPredecessors(),可以看到该方法主要做一件事情:主要是判断当前线程是否位于同步队列中的第一个。如果是则返回true,否则返回false。
综上,公平锁就是通过同步队列来实现多个线程按照申请锁的顺序来获取锁,从而实现公平的特性。非公平锁加锁时不考虑排队等待问题,直接尝试获取锁,所以存在后申请却先获得锁的情况。
可重入锁 非可重入锁
可重入锁:当线程获取外层函数的锁时,在访问内层函数可以直接获取锁,这就是可重入锁,但是有个前提是:必须是同一对象或者同一class。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
不可重入锁:当线程获取外层函数的锁事,在访问内层函数时仍然需要加锁。
通过查看源码:可重入锁:当要获取锁时,如果status是0,那么直接获取执行。如果不为0,判断持有锁的线程是否就是当前线程,如果是则status+1。当释放锁时,也是判断当前线程是否是占有锁的线程,然后判断status是否为0,为0表示可以释放,将持有当前锁的线程设为null。
非可重入锁:获取锁时,直接使用CAS去尝试更新status,如果更新失败,当前线程阻塞。释放锁时,确定持有锁的线程就是当前线程的时候,就直接将status赋值为1,然后持有当前锁的owner为null。