项目

Java开发中的各种锁概念

2018-12-18  本文已影响19人  __y

前言:
本文不会深入不会深入!科普文,就是归纳一下平时我们遇到的各种锁,这样听到也不会太懵逼。真正深入的还是要看书的~
在Java开发中,特别是并发编程的时候我们会和很多的锁打交道。写下一篇笔记记录一下各种锁的概念。

1.死锁

在学习操作系统,或者并发编程中的时候我们经常会遇到死锁的概念。什么是死锁?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
举个简单的例子:
一个线程需要抢占A锁去执行某段代码,抢占到了A锁后,要抢占B锁去继续执行代码。另外一个线程则需要抢占B锁,然后抢占A锁去执行代码。这样就会造成彼此之间阻塞。两个线程一起卡死。
如何解决?

2.显示锁,公平锁,可重入锁

标题中的说的三种锁说的就是Lock这个接口下的锁,Lock具有这三种概念。
显示锁:非Java给我们提供的关键字去操作。而是我们自己定义锁,然后显示的获取,显示的释放。
公平锁:一定程度上保证线程获得这个锁的机会是公平的。但是这样会大大消耗性能。
可重入锁:看下面的代码。这两个是同步代码块。他们获取的是同一把锁。因此获取锁的是线程,不是对象。因此在线程没有结束之前,这把锁可以一直使用。
PS:Lock下的ReentrantLock也具有可重入性。

public class Widget {
    // 获得了锁
    public synchronized void doSomething() {
        ...
    }
}

public class LoggingWidget extends Widget {

    // 获得了锁
    public synchronized void doSomething() {
        System.out.println(toString() + ": calling doSomething");
        super.doSomething();
    }
}

3.Lock的介绍

其实synchronize从jdk一直发展到现在,性能已经非常好。如果不是很有必要还是建议synchronize;因为简单易用。不过这里还是要说一下Lock。下面是这个接口的方法。

image.png

看上去是非常爽的。也非常的优雅,毕竟啥时候加锁上锁的控制权在我们这里了。但是如果我们忽略了释放锁,或者说程序出了点问题,这个锁没有释放。那就麻烦大了~因此还是要酌情使用!

怎么用呢?这里给一个demo

class LockDemo {
    private final ReentrantLock lock = new ReentrantLock();
    // ...

    public void m() { 
        lock.lock();  // block until condition holds
        try {
            // ... method body
        } finally {
            lock.unlock()
        }
    }
}


4.ReentrantReadWriteLock

ReentrantReadWriteLock是一个读写锁

package com.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockTest2 {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        final LockTest2 lockTest2 = new LockTest2();
        new Thread(new Runnable() {
            @Override
            public void run() {
                lockTest2.get(Thread.currentThread());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                lockTest2.get(Thread.currentThread());
            }
        }).start();
    }

    public void get(Thread thread) {
        try {
            lock.readLock().lock();
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName() + "正在进行读操作");
            }
            System.out.println(thread.getName() + "操作完毕");

        }catch (Exception e) {

        }
        finally {
            lock.readLock().unlock();
        }
    }

}
image.png

我们可以看到是交替进行的~
写锁:
写锁只有将readLock改成writeLock就好了。这里不贴代码了。直接观察结果可以看出是互斥的。必须一个个来


image.png

5.悲观锁,乐观锁

悲观锁,乐观锁也是我们并发编程中常遇到的问题。在数据库层面有为体现。
下面举例子说明:

image.png
假设现在有两个线程:
A线程来了,读到了这个数据,status为0;B线程改为了1了。由于数据库的事务隔离机制。并发修改了这个数据。那数据就混乱了。
这个时候就要保持互斥性,同一个时间只能有一个线程来操作这行数据。
解决方案:
乐观锁:
加一个字段版本字段:
A线程读出来的时候版本号位1,然后更新的时候加个条件update xxx where version =1;但是B线程已经改为了2,所以会更新失败;这样就可以控制并发修改。一般是给用户提示,有人已经操作了数据。但是还是可以读取这一行数据
悲观锁
SQL关键字FOR_UPDATE
select * from XXXX FOR_UPDATE
把查询出来的数据锁住了,其他人读这条数据会阻塞。在用户界面上会转圈会等待。
举例:
比如外卖。用户下了一个单,这个时候订单的数据异步到配送端了。此时,用户突然申请退单了。此时数据库订单状态应该是退单状态。然而由于时差,订单的数据才到配送端。此时配送人员又抢单成功将订单状态重新改为了配送中(实际上这张订单应该是退单状态的。)

6.分布式锁

我们知道乐观锁,悲观锁是数据库层面(单个)。如果我们分库分表的话我们怎么处理呢?这个时候我们就要用分布式锁。
我们可以用zookeeper,redis实现~具体实现大家可以baidu一下!

上一篇 下一篇

猜你喜欢

热点阅读