11-1. Java中的重入锁、读写锁、自定义锁实现

前言:上一节讲述了锁的原理,这节先讲解锁的应用,再通过上节的原理来实现一个自定义的锁。

1 从锁开始讲起

1.1 lock

在java.util.concurrent.locks.Lock.java中的源码解释:

根据Lock接口的源码注释,Lock接口的实现,具备和同步关键字同样的内存语义。

lock的常用API
lock.lock(); // 如果一个线程拿到锁,其他线程会等待
lock.tryLock(); // 尝试获取锁,获取不到立即返回
lock.tryLock(1000L); // 尝试获取锁1秒,获取不到也立即返回

1.1.1 可重入锁ReentrantLock

package szu.vander.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author : Vander
 * @date :   2019/12/7
 * @description : 可重入锁
 */
public class ReentrantDemo {

    private final static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args){
        reentrantLock.lock();
        try {
            System.out.println("第1次获取锁");
            System.out.println("this thread lock hold count : " + reentrantLock.getHoldCount());
            reentrantLock.lock();
            System.out.println("第2次获取锁");
            System.out.println("this thread lock hold count : " + reentrantLock.getHoldCount());
        } finally {
            reentrantLock.unlock();
            reentrantLock.unlock();
        }
        System.out.println("this thread lock hold count : " + reentrantLock.getHoldCount());

        new Thread(() -> {
            System.out.println(Thread.currentThread() + ": expect to get the lock!");
            reentrantLock.lock();
            System.out.println(Thread.currentThread() + ": had get the lock!");
        }).start();

    }

}

运行结果

这里说明一下ReentrantLock有公平和非公平之分。(公平锁就是哪个线程等得久就让哪个线程先执行)

1.1.2 可响应中断的ReentrantLock

package concurrent.lock;

import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author : Vander
 * @date :   2019/12/8
 * @description : 获取锁后可被中断
 */
public class LockInterruptiblyDemo {

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        LockInterruptiblyDemo lockInterruptiblyDemo = new LockInterruptiblyDemo();
        Runnable runnable = () -> {
            try {
                lockInterruptiblyDemo.getLockAndInterrupt();
            } catch (InterruptedException e) {
                Instant now = Instant.now();
                System.out.println(String.format("%s - %s : in the main runnable func, being interrupt, %s"
                        , now
                        , Thread.currentThread()
                        , e.toString()));
            }
        };
        Thread thread0 = new Thread(runnable);
        Thread thread1 = new Thread(runnable);

        try {
            thread0.start();
            Thread.sleep(500); // 等待0.5秒,让thread0先执行

            thread1.start();
            Thread.sleep(2000); // 两秒后,中断thread1

            thread1.interrupt(); // 处于Sleep状态或处于lockInterruptibly状态能被中断
        } catch (InterruptedException e) {
            Instant now = Instant.now();
            System.out.println(String.format("%s - %s : in the main func, being interrupt, %s"
                    , now
                    , Thread.currentThread()
                    , e.toString()));
        }

    }

    public void getLockAndInterrupt() throws InterruptedException {
        Instant now = Instant.now();
        System.out.println(String.format("%s - %s : expect the this lock", now, Thread.currentThread()));
        lock.lockInterruptibly();// 阻塞,能立即响应中断,lock.lock(),阻塞且不立即响应中断
        try {
            now = Instant.now();
            System.out.println(String.format("%s - %s : get the this lock" +
                    ", and start sleep 10s", now, Thread.currentThread()));
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            now = Instant.now();
            System.out.println(String.format("%s - %s : in the getLockAndInterrupt func, being interrupt, %s"
                    , now
                    , Thread.currentThread()
                    , e.toString()));
        } finally {
            now = Instant.now();
            System.out.println(String.format("%s - %s : run into finally", now, Thread.currentThread()));
            lock.unlock();
            System.out.println(String.format("%s - %s : release the this lock", now, Thread.currentThread()));
        }

    }

}

运行结果:线程0获得了锁,并处于Sleep状态,线程1进入阻塞并且能响应中断的状态,将线程1中断,线程1可以响应中断。

2)改为lock.lock()

运行结果:改成lock()之后,线程1就不立即响应中断了,它会一直等锁,然后进入Sleep的时候才发现中断状态为已经被改变了,才去响应中断

1.1.3 读写锁(ReadWriteLock)

为了提高读操作比写操作多的场景的性能,设计出了读写锁的思路。当某个线程进行写操作时,所以进行读操作的线程都不能来读,因为这样有可能会读取到脏数据;另一种情况是,如果某个线程仅仅是来读数据的,总不能让其它读数据的线程不能来读吧,所以这种情况下的读锁是可以共享的,属于共享锁。而第一种情况的写数据时,锁是互斥的,属于互斥锁,也称为独享锁

设计思路:维护一对关联锁,一个用于只读操作,一个用于写入操作;
读锁可以由多个读线程同时持有,而写锁是排他的,互斥的。

示例场景
缓存组件、集合的并发线程安全性改造。

锁降级(解决读取两次数据的数据不一致问题)
写锁是线程独占,读锁是共享,所以写->读是降级。(读->写,是不能实现的,因为持有读锁的情况下,是不能再持有写锁的)
在读写锁中还会提到的概念是锁降级指的是写锁降级成为读锁。持有当前拥有的写锁的同时,再获取到读锁,随后释放写锁的过程。

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author : Vander
 * @date :   2019/12/8
 * @description : 这里既是读写锁的一种常见应用,也是锁降级的常用之处
 */
public class CacheDataDemo<T> {

    /**
     * hashMap不是线程安全的
     */
    private Map<String, T> cache = new HashMap<>();

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public T get(String key) {
        // 获取读锁,保证读期间,数据没有被修改
        readWriteLock.readLock().lock();
        if(cache.get(key) == null) {
            // 从数据库中读取,并写入缓存
            try {
                T value = getDataFromDB(key);
                // 先释放读锁
                readWriteLock.readLock().unlock();
                // 获取写锁
                readWriteLock.writeLock().lock();
                // 再次判断是否对应的Key的缓存为空,这是为了防止获取写锁的过程中,已经有其它线程写入了缓存
                // 往缓存中写数据
                if(cache.get(key) == null) {
                    cache.put(key, value);
                }
                // 锁降级:持有写锁过程中,再获取读锁,再释放写锁:写锁->读锁
                readWriteLock.readLock().lock();
            } finally {
                // 进行后续操作,可能需要继续进行读取数据的操作
                readWriteLock.writeLock().unlock();
            }

        }
        T value = cache.get(key);
        readWriteLock.readLock().unlock();
        return value;
    }

    private T getDataFromDB(String key) {
        // 模拟从数据库中读取数据
        return (T)new Object();
    }

}

2.1 实现一个自定义的锁

2.1.1 从原理开始讲解

上一节讲解了synchronized同步关键字,从偏向锁->轻量级锁->重量级锁,自己实现的Lock,先不实现这么复杂的东西,从简单的思路出发,我们直接来实现一个简单的重量级锁,重量级锁中有两个重要的属性:锁池(等待池,即多个线程争抢这个锁,记录下这些线程争抢这个锁的Pool)、锁的拥有者,这里我们先实现一个不可重入的锁,就先不记录锁的状态了。

基本思路:


package szu.vander.lock;

import szu.vander.log.LogLevel;
import szu.vander.log.Logger;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

/**
 * @author : Vander
 * @date :   2019/12/8
 * @description : 自定义Lock实现
 */
public class UserDefinedLock implements Lock {

    private final static Logger log = new Logger(false, LogLevel.INFO.getLevel());

    /**
     * 存放争抢锁的线程信息
     */
    private volatile LinkedBlockingQueue<Thread> waitLockPool = new LinkedBlockingQueue<>();
    /**
     * 当前锁的拥有者,存放线程信息
     */
    private volatile AtomicReference<Thread> owner = new AtomicReference<>();

    @Override
    public boolean tryLock() {
        if (owner.get() == null) {
            return setOwner(Thread.currentThread());
        }
        return false;
    }

    @Override
    public void lock() {
        // 锁池中的每个线程都要来尝试争抢锁
        while (!tryLock()) {
            // 将等待锁的线程放入等待池中
            waitLockPool.add(Thread.currentThread());
            // 对于没有抢到锁的线程全都阻塞
            log.info("begin to park");
            LockSupport.park(Thread.currentThread());
        }
        waitLockPool.remove(Thread.currentThread());
    }

    @Override
    public void unlock() {
        // 当前线程执行了解锁
        if (owner.compareAndSet(Thread.currentThread(), null)) {
            // 通知等待池中的所有线程去争抢锁
            for (Thread thread: waitLockPool) {
                // 此处如果使用waitLockPool.poll去遍历,速度将会非常慢
                LockSupport.unpark(thread);
            }
        }
    }

    /**
     * 使用CAS机制来修改owner的值
     *
     * @param owner
     */
    private boolean setOwner(Thread owner) {
        return this.owner.compareAndSet(null, owner);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }

    public static void main(String[] args) {
        Lock lock = new UserDefinedLock();
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                lock.lock();
                log.info("get the this lock, and start sleep");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("release the this lock");
                lock.unlock();
            }
        };
        for (int i = 0; i < 5; i++) {
            new Thread(runnable1).start();
        }

    }

}

测试代码:

package szu.vander.test.lock;

import szu.vander.lock.UserDefinedLock;

import java.util.concurrent.TimeUnit;

/**
 * @author : Vander
 * @date :   2019/12/8
 * @description :
 */
public class UserDefinedLockTest {

    private static int sum = 0;

    private static UserDefinedLock lock = new UserDefinedLock();

    private static int add() {
        lock.lock();
        sum++;
        lock.unlock();
        return sum;
    }

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    add();
                }
            }
        };
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
        TimeUnit.SECONDS.sleep(5);
        System.out.println("Result : " + sum);
    }


}

运行结果:使用10个线程同时进行累加,不会出现加不满10w的情况,说明自定义锁也不会存在并发问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容