背景

在前面几篇文章中介绍了,线程、线程池、异步。但在有些场景下,需要线性执行,这样才能避免出现错乱,比如银行账户的余额,我们首先想到的就是加锁。在java中锁分为几大类,单应用与分布式应用又该怎么处理,在什么场景下用什么锁?

java中常用锁的状态

无锁状态
偏向锁状态
轻量级锁状态
重量级锁状态

java中常用锁的升级过程

其实锁的状态对应的是不同的场景
无锁:这个比较好理解,没有任何资源锁定,大家均可以访问修改
偏向锁:不存在锁竞争,但一个线程需要多次访问获得同一锁,如果此时锁竞争、加锁、解锁等会浪费资源,偏向锁实现了根据线程ID判断是否是同一线程,如果是,直接使用
轻量级锁(自旋锁):不需要cpu介入,线程自己自炫释放,这样节省了cpu的开销。此锁针对线程持有锁的时间不长。
重量级锁:自旋时间过长、锁持有时间过长,会升级为重量级锁。它的底层一来是mutex互斥锁,由操作系统负责调度

java中常用锁的种类

悲观锁:比如synchronized,每次调用都会主动加锁
乐观锁:每次调用都不认为别人会参与,如果被别人参与更新了,就拒绝更新,常用的version版本控制
公平锁:按照申请锁的顺序获取锁,先到先得,公平公正。
非公平锁:比如synchronized,获取锁的方式是随机的,会存在线程饿死,一直拿不到锁
可重入锁:例如:synchronized,ReentrantLock,也叫递归锁,在外层使用锁之后,在内层仍然可以使用,并且不会产生死锁
不可重入锁:在当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
自旋锁:一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,任何时刻最多只能有一个执行单元获得锁,很容易陷入空旋
互斥锁:只能有一个线程访问该对象,保证共享数据操作的完整性
共享锁:也叫读锁,仅可以查看数据,但不能修改和删除
独享锁:互斥锁

java实战常用锁方式

synchronized关键字,如下:
public synchronized void test2() {
        // 锁的释放:1、执行完 2、发生异常
        // 锁的状态无法判断 可重入、不可中断、非公平
}

Lock锁,ReentranLock是Lock的唯一实现类
private Lock lock = new ReentrantLock();
public void test3(){
        // 锁
        lock.lock();

        // 解锁
        lock.unlock();
        // 实际应用过程中,一定要确保锁释放,比如抛异常时的处理
}

分布式锁

在上面的实例中,可以看出,当是单应用时,锁没有问题,但如果是为服务,多应用,则无法满足
针对这一情况,需要有一个公共资源的地方,来控制是否加锁
1、基于数据库锁
2、基于zookpeer
3、基于redis
在此先不做详细介绍,有兴趣的小伙伴,可以根据此思路,搜索一下