并发编程--Java中的锁(一)应用

一、Lock接口

1.简介 

      与synchronized类似都是用来 控制多个 线 访问 共享 源的方式,但是它缺少了(通过synchronized块或者方法所提 供的)隐 的便捷性,却 有了 锁获 取与 放的可操作性、可中断的 以及超时获 等多种 synchronized 字所不具 的同步特性。
        所以在一些需要灵活操作锁的场景时例如先获得锁A,然后再获取锁B,当锁B获得后,释放锁A同时获取锁C,当锁C获得后,再释放B同时获取锁D场景下使用 Lock接口就比较合适
Lock lock = new ReentrantLock(); 
lock.lock(); 
try { 
    //执行同步逻辑
} finally { 
    lock.unlock(); 
}

2.Lock接口提供的synchronized字不具的主要特性

3.LockAPI

 二、ReentrantLock

        重入锁 ReentrantLock 名思 ,就是支持重 入的 ,它表示 该锁 支持一个 线
源的重复加 。除此之外, 该锁 支持 锁时 的公平和非公平性 选择

1.可重入:

public class ReentrantLockTest {

    public static void main(String[] args) throws InterruptedException {

        ReentrantLock lock = new ReentrantLock();

        for (int i = 1; i <= 3; i++) {
            lock.lock();
        }

        for(int i=1;i<=3;i++){
            try {

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

 2.上面的代码通过lock()方法先获取锁三次,然后通过unlock()方法释放锁3次,程序可以正常退出。从上面的例子可以看出,ReentrantLock是可以重入的锁,当一个线程获取锁时,还可以接着重复获取多次。在加上ReentrantLock的的独占性,我们可以得出以下ReentrantLock和synchronized的相同点。

  • 1.ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。但是实现上两者不同:synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。

  • 2.ReentrantLock和synchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。

ps:虽然ReentrantLock叫可重入锁,但是其实jdk中所有的锁都支持可重入

2.公平锁

        公平性与否是针对获 而言的,如果一个 锁是公平的,当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。而非公平锁则随机分配这种使用权。和synchronized一样,默认的ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。当然公平锁能防止饥饿,某些情况下也很有用。在创建ReentrantLock的时候通过传进参数 true创建公平锁,如果传入的是 false或没传参数则创建的是非公平锁

// 创建的是公平锁
private ReentrantLock lock = new ReentrantLock(true);

// 创建的是非公平锁
private ReentrantLock lock = new ReentrantLock(false);

// 默认创建非公平锁
private ReentrantLock lock = new ReentrantLock();

3.可响应中断

当使用synchronized实现锁时,阻塞在锁上的线程除非获得锁否则将一直等待下去,也就是说这种无限等待获取锁的行为无法被中断。而ReentrantLock给我们提供了一个可以响应中断的获取锁的方法lockInterruptibly()。该方法可以用来解决死锁问题。

public class ReentrantLockTest {
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(new ThreadDemo(lock1, lock2));//该线程先获取锁1,再获取锁2
        Thread thread1 = new Thread(new ThreadDemo(lock2, lock1));//该线程先获取锁2,再获取锁1
        thread.start();
        thread1.start();
        thread.interrupt();//是第一个线程中断
    }

    static class ThreadDemo implements Runnable {
        Lock firstLock;
        Lock secondLock;
        public ThreadDemo(Lock firstLock, Lock secondLock) {
            this.firstLock = firstLock;
            this.secondLock = secondLock;
        }
        @Override
        public void run() {
            try {
                firstLock.lockInterruptibly();
                TimeUnit.MILLISECONDS.sleep(10);//更好的触发死锁
                secondLock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                firstLock.unlock();
                secondLock.unlock();
                System.out.println(Thread.currentThread().getName()+"正常结束!");
            }
        }
    }
}

如果不使用lockInterruptibly()那么当线程1中断时,finally中断的代码就不会被执行锁就无法释放,线程二需要线程1释放锁才能执行,所以导致堵塞,而lockInterruptibly()可以监控到中断从而释放锁避免死锁情况发生

4.超时等待

ReentrantLock还给我们提供了获取锁限时等待的方法tryLock(),可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。我们可以使用该方法配合失败重试机制来更好的解决死锁问题。

  //,此时如果在1秒内没有获得锁那么会返回false
        boolean lockSucceed = reentrantLock.tryLock(1, TimeUnit.SECONDS);

三、读写锁(ReadWriteLock

        前面的锁都是排他锁 在同一 刻只允 一个 线程进 访问 ,而 在同一 刻可以允 多个 读线 访问 ,但是在写 线 访问时 ,所有的 读线程和其他写 线 程均被阻塞。 锁维护 了一 对锁 ,一个 读锁 和一个写 ,通 分离 读锁 和写锁,使得并 性相比一般的排他 有了很大提升。
Java 包提供 实现 是ReentrantReadWriteLock

1.特性

 2.提供的方法

        ReadWriteLock仅 读锁 和写 的两个方法,即 readLock() 方法和 writeLock()
法,而其 实现 ——ReentrantReadWriteLock ,除了接口方法之外, 提供了一些便于外界 控其
内部工作状 的方法

 3.样例实现

    static Map<String, String> map = new HashMap<>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    public static void main(String[] args) {
        map.put("test","0000");
        for (int i = 0; i < 10; i++) {
            new Thread(new ReadThread()).start();
            if(i == 5){
                new Thread(new WriteThread()).start();
            }
        }
    }
    static class ReadThread implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep((int)Math.random()*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            getData("test");
        }
    }
    static class WriteThread implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep((int)Math.random()*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            setData("test","1234");
        }
    }
    public static String getData(String key) {
        r.lock();
        try {
            System.out.println(map.get(key));
            return map.get(key);
        } finally {
            r.unlock();
        }
    }

    public static String setData(String key, String value) {
        w.lock();
        try {
            System.out.println("写入成功");
            return map.put(key, value);
        } finally {
            w.unlock();
        }
    }

执行结果:

 上述示例中,Cache合一个非线程安全的HashMap为缓存的实现,同使用

读锁 和写 来保 Cache 线 程安全的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值