并发编程:ReentrantLock应用

1. 特点

  1. 可打断,可重入
  2. 可以设置超时时间
  3. 可以设置为公平锁
  4. 支持多个条件变量
  5. 支持读写锁
  • 基本语法
reentrantLock.lock();
try {
    //业务代码
} finally {
    reentrantLock.unlock();
}

2. 重入

package org.example;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class ReEntryLock {
    static ReentrantLock lock = new ReentrantLock();

    public static void lock1() {
        lock.lock();
        try {
            log.info("执行lock1");
        } finally {
            lock.unlock();
        }
    }
    public static void lock2() {
        lock.lock();
        try {
            log.info("执行lock2");
            //重入
            lock1();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        lock2();
    }

}
  • 查看打印

在这里插入图片描述

3. 可打断

t—线程
lock.lockInterruptibly();
标识可以打断
怎么打断
t.interrupt();

package org.example;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class InterruptLock {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        //t1先获取锁,然后睡眠5s
        new Thread(() -> {
            try {
                lock.lock();
                log.info("t1获取锁");
                TimeUnit.SECONDS.sleep(5);
                log.info("t1睡眠了5s");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }, "t1").start();
        TimeUnit.SECONDS.sleep(1);
        //t2加锁失败,因为被t1持有了
        Thread t2 = new Thread(() -> {
            try {
                lock.lockInterruptibly();
                log.info("t2获取锁了,开始执行代码");

            } catch (InterruptedException e) {
                log.info("t2被打断了,没有获取到锁");
            } finally {
                lock.unlock();
            }
        }, "t2");
        t2.start();

        //由于t2可以被打断,所以1s后打断t2,不在等待t1释放锁
        log.info("主线程睡眠1s后打断t2");
        TimeUnit.SECONDS.sleep(2);
        t2.interrupt();
    }

}
  • 查看打印

在这里插入图片描述

4. 超时

lock.tryLock(2, TimeUnit.SECONDS)

package org.example;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class OutTimeLock {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Thread t1=new Thread(()->{
            log.info("t1启动");
            try {
                //尝试获取锁,如果失败就返回
                if (!lock.tryLock(2, TimeUnit.SECONDS)) {
                    log.info("t1获取锁失败");
                    return;
                }
                try {
                    log.info("t1获得锁");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");

        try {
            lock.lock();
            log.info("主线程获得了锁");
            t1.start();
            TimeUnit.SECONDS.sleep(3);
        } finally {
            lock.unlock();
        }
    }

}
  • 查看打印

在这里插入图片描述

5. 多条件

  • synchronized 中就有条件变量,就是waitSet,不满足条件的线程进入waitSet;而Lock也有waitSet而且有多个

创建条件
Condition waitA = lock.newCondition()
阻塞
waitA.await();
唤醒
waitA.signal();

package org.example;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class MultiConditionLock {

    static ReentrantLock lock = new ReentrantLock();
    static boolean aFlag = false;
    static boolean bFlag = false;

    static Condition waitA = lock.newCondition();
    static Condition waitB = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            lock.lock();
            try {
                log.info("aFlag="+aFlag);
                while (!aFlag) {
                    log.info("aFlag="+aFlag);
                    try {
                        waitA.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("aFlag="+aFlag);
            } finally {
                lock.unlock();
            }
        },"a").start();

        new Thread(()->{
            lock.lock();
            try {
                log.info("bFlag="+bFlag);
                while (!bFlag) {
                    log.info("bFlag="+bFlag);
                    try {
                        waitB.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("bFlag="+bFlag);
            } finally {
                lock.unlock();
            }
        },"b").start();

        Thread.sleep(1000);
        new Thread(()->{
            lock.lock();
            try {
                aFlag = true;
                log.info("修改aFlag = true;");
                waitA.signal();
                bFlag = true;
                log.info("修改bFlag = true;");
                waitB.signal();
            } finally {
                lock.unlock();
            }

        }).start();
    }
    
}
  • 查看打印

在这里插入图片描述

6. 读写锁

  • 读写互斥
  • 写写互斥
  • 读读并发

读写锁读锁不支持条件,ReentrantReadWriteLock的 newCondition 会直接exception

在这里插入图片描述

6.1 读写互斥

package org.example;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ReadWriteLock {

    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    public static void m(String str) {
        log.info(str);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //读
        new Thread(()->{
            log.debug("读锁");
            r.lock();
            try {
                for (int i=0; i < 5; i++) {
                    m("读锁" + i);
                }
            } finally {
                r.unlock();
            }
        },"t1").start();
        //写
        new Thread(()->{
            log.debug("写锁");
            w.lock();
            try {
                for (int i=0; i < 5; i++) {
                    m("写锁" + i);
                }
            } finally {
                w.unlock();
            }
        },"t2").start();
    }

}
  • 查看打印,写锁先获取锁,则先执行写锁业务代码

在这里插入图片描述

  • 读锁先获取锁,则先执行读锁业务代码

在这里插入图片描述

6.2 写写互斥

package org.example;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ReadWriteLock {

    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    public static void m(String str) {
        log.info(str);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //写
        new Thread(()->{
            log.debug("写锁");
            w.lock();
            try {
                for (int i=0; i < 5; i++) {
                    m("写锁" + i);
                }
            } finally {
                w.unlock();
            }
        },"t1").start();
        //写
        new Thread(()->{
            log.debug("写锁");
            w.lock();
            try {
                for (int i=0; i < 5; i++) {
                    m("写锁" + i);
                }
            } finally {
                w.unlock();
            }
        },"t2").start();
    }

}
  • 查看打印,t2写锁先获取锁,先执行t2线程业务代码

在这里插入图片描述

6.3 读读并发

  • 假设有6个线程,t1正在持有锁,队列内有5个线程(t2–t6)正在等待t1释放锁;
  • 当t1释放锁,按照FIFO的原则依次唤醒队列内的线程;
  • 如果t2是写锁,则t3继续等待,当t2释放锁后,t3获得锁;
  • 如果t3是读锁,那t3会判断t4是不是读锁,如果t4也是读锁,会把t4唤醒,t4唤醒后也会判断t5(假设t5不是读锁)是不是读锁,依此类推,直到后面的锁不是读锁为止,这时t3和t4并发执行;
  • 如果t5是写锁,t6是读锁,t6也只能等到t5释放锁后,再执行;
  • 读读并发指的是连续的读锁可以并发执行。

在这里插入图片描述

package org.example;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ReadWriteLock {

    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    public static void m(String str) {
        log.info(str);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //读
        new Thread(()->{
            log.debug("读锁");
            r.lock();
            try {
                for (int i=0; i < 5; i++) {
                    m("读锁" + i);
                }
            } finally {
                r.unlock();
            }
        },"t1").start();
        //读
        new Thread(()->{
            log.debug("读锁");
            r.lock();
            try {
                for (int i=0; i < 5; i++) {
                    m("读锁" + i);
                }
            } finally {
                r.unlock();
            }
        },"t2").start();
    }

}
  • 查看打印,t1和t2并发执行

在这里插入图片描述

6.4 读写支持重入,但是只支持降级,不支持升级

  • 先写锁(降级),在读锁
package org.example;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ReadWriteLock {

    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    public static void main(String[] args) {
        new Thread(()->{
            try {
                w.lock();
                log.debug("write已经获取");
                r.lock();
                log.debug("read已经获取");
            } finally {
                r.unlock();
                w.unlock();
            }
        },"t1").start();
    }

}
  • 查看打印

在这里插入图片描述

  • 先读锁,在写锁
package org.example;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ReadWriteLock {

    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    public static void main(String[] args) {
        new Thread(()->{
            try {
                r.lock();
                log.debug("read已经获取");
                w.lock();
                log.debug("write已经获取");
            } finally {
                w.unlock();
                r.unlock();
            }
        },"t1").start();
    }

}
  • 查看打印,写锁不能获取

在这里插入图片描述

  • 锁升级(先读后写),会发生死锁
  • 假设有2个线程,如果线程t1先获取读锁,这时候线程t2来了,因为读读并发,所以t2也获取读锁;
  • t1继续执行,尝试获取写锁,因为读写互斥,t1一定阻塞,需要等待t2释放读锁,然后t1才能获取写锁,但是t2正常情况下,不会主动释放读锁,所以相互等待。

6.5 缓存场景应用

package org.example;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ReadWriteLock {

    //缓存变量
    static Object data = "我是缓存中的数据";
    //过期标志,true没过期,直接走缓存;false过期,先查数据库更新缓存,再走缓存
    static volatile boolean cacheValid = false;
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    public static void main(String[] args) {
        r.lock();
        //如果缓存过期,则查数据库;没过期,则直接使用data
        if (!cacheValid) {
            //要拿写锁,需要先释放读锁,因为不支持重入升级
            r.unlock();
            //要去load真实数据,set给缓存拿到写锁
            w.lock();
            try {
                //双重校验
                if (!cacheValid) {
                    //更新缓存
                    data = "我是数据库中的数据";
                    cacheValid = true;
                }
                //更新缓存后,需要读取数据,所以加读锁
                r.lock();
            } finally {
                w.unlock();
            }
        }
        try {
            //使用缓存数据
            System.out.println(data);
        } finally {
            r.unlock();
        }
    }
    
}

在这里插入图片描述
在这里插入图片描述

7. Lock与synchronized区别

1、synchronized是Java中的关键字;Lock是jdk中的一个interface接口
2、synchronized修饰的代码在执行异常时,jdk会自动释放锁;Lock发生异常时,需要手动释放,否则会造成死锁,一般都是在finally中
3、synchronize可以用在代码块上,方法上;lock只能写在代码里,不能直接修改方法
4、synchronize是非公平锁;Lock支持公平锁,默认非公平锁
5、synchronize不可中断,只能等待锁的释放;ReentrantLock提供了lockInterruptibly()的打断功能,中断直接抛出异常,可以对打断做出响应
6、synchronized使用Object对象本身的wait、notify、notifyAll调度机制,要么随机唤醒,要么全部唤醒;Lock支撑多条件,可以精准唤醒
7、synchronized是独占锁;ReentrantLock支持读写锁,读锁可以并发,写锁只能独占,还支持锁降级

性能几乎一样,但是在jdk1.8之前,ConcurrentHashMap使用Lock加锁,jdk1.8之后,就换成synchronized,姑且猜测synchronized的性能可能还高于Lock

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值