Java有很多种锁:公平锁、非公平锁、可重入锁、递归锁、自旋锁、读锁、写锁、等等
公平和非公平锁
java.util.concurrent.locks.ReentrantLock
可以通过指定构造函数的boolean类型来得到公平锁或者非公平锁,默认情况下将构造非公平锁。
是什么
公平锁:是指多个线程按照申请锁的顺序来获取锁,类似于排队,先来后到。
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程更有先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。
//Creates an instance of ReentrantLock. This is equivalent to using ReentrantLock(false).
public ReentrantLock() {
sync = new NonfairSync();
构造方法有两个:
ReentrantLock()
ReentrantLock(boolean fair)
根据给定的公平政策创建一个 ReentrantLock
的实例。(默认情况下创建的为非公平锁)
两者之间的区别
公平锁:并发环境下,每个线程获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的一个,就占有锁,否则就睡加入到等待队列中,以后会按照FIFO规则从队列中取到自己。
非公平锁:比较粗鲁,一开始就直接尝试占有锁,如果尝试失败,再采用类似公平锁的方式。
- 非公平锁的优点在于吞吐量比公平锁大
- 对于
Synchronized
而言,也是非公平锁。
-------------------------------------------(分割线)--------------------------------------------------
可重入锁(又名递归锁)(ReentrantLock)
是什么
可重入锁,也叫递归锁。指的是
- 同一线程外层函数获得锁之后,内层递归函数仍能获得该锁的代码。
- 在同一个线程在外层方法获取锁的时候,再进入内层方法会自动获取锁。
即: 线程可以进入任何一个他已经拥有的锁所同步着的代码块。
//外层函数
public synchronized void method01(){
method02();//M2的锁由于M1 获得了锁而自然获得
}
//内层函数
public synchronized void method02(){}
ReentrantLock/Synchronize都是典型的可重入锁(默认非公平)
可重入锁最大的作用就是避免死锁
死锁,M1获得了锁,调用M2,M2一直等待M1结束,M1会死锁
一系列流程使用同一个锁,执行方法1时候锁住,执行方法2时需要等待自己在方法1 中获得的锁解掉,形成了自己需要获得锁,自己又需要解掉锁的死锁情况。
可重入锁的种类
- 隐式锁(即
Synchronized关键字使用的锁
)默认是可重入锁 - 显式锁(即
Lock
)也有ReentrantLock
这样的可重入锁
ReentrantLockDemo
class Phone implements Runnable
{
public synchronized void sendSMS()throws Exception{
System.out.println(Thread.currentThread().getName()+"\t-----------invoked sendSMS");
sendEmail();
}
public synchronized void sendEmail()throws Exception{
System.out.println(Thread.currentThread().getName()+"\t+++++++++++invoked sendEmail");
}
//----------------------------------------------------------------------
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
private void get(){
//加锁要和解锁个数配对!!(编译运行都不会报错,会有锁滞留)
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"\t-----------invoked get");
set();
}finally {
//加锁要和解锁个数配对!!(编译运行都不会报错,会有锁滞留)
lock.unlock();
}
}
private void set() {
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"\t+++++++++++invoked set");
}finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
System.out.println("-----------------------Synchronized");
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"t2").start();
try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("-----------------------ReentrantLock");
new Thread(()->{
phone.run();
},"t3").start();
new Thread(()->{
phone.run();
},"t4").start();
}
}
Console:
-----------------------Synchronized
t1 -----------invoked sendSMS
t1 +++++++++++invoked sendEmail
t2 -----------invoked sendSMS
t2 +++++++++++invoked sendEmail
-----------------------ReentrantLock
t3 -----------invoked get
t3 +++++++++++invoked set
t4 -----------invoked get
t4 +++++++++++invoked set
加锁要和解锁个数配对!!(编译运行都不会报错,会有锁滞留)
自旋锁
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式取尝试获取锁,
- 这样做的好处是减少线程上下文切换的消耗
- 缺点是循环会消耗CPU。
- 循环比较获取,直到成功为止,没有类似wait的阻塞。
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//预期值和主物理内存中的值不一致就一直重新取(可能会出现多次循环)
return var5;
}
手写一个自旋锁
package com.company;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
//原子引用线程
AtomicReference atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+" came in for the Lock");
while (!atomicReference.compareAndSet(null,thread)){
//空循环-> 直到atomicReference为null时,执行CAS,并跳出循环
}
}
public void unLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(thread.getName()+" release the Lock");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
System.out.println("Thread A get the Lock");
try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
spinLockDemo.unLock();
},"A").start();
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
spinLockDemo.myLock();
System.out.println("Thread B get the Lock");
spinLockDemo.unLock();
},"B").start();
}
}
----------------------------------------------------------------------------------------------------
Console:
A came in for the Lock
Thread A get the Lock
B came in for the Lock
(5S后)
A release the Lock
Thread B get the Lock
B release the Lock