并发编程12-显示锁

内部所拥有比较好的性能,但是在灵活性方面有缺陷,并且如果申请锁失败就会陷入阻塞等待的过程中。
对于一些场景,我们可以使用显示锁Lock

基本应用

Lock 的lock方法相当于进入同步块, unlock方法相当于退出同步块。支持跟内部锁同样的重入机制:

    public void displayLock(){
        Lock lock = new ReentrantLock();
        lock.lock();
        try{

        }finally{
            lock.unlock();
        }
    }

    public void innerLock(){
        Object lock = new Object();
        synchronized (lock){

        }
    }

可以看出内部锁的代码还有清爽一点, 显示锁需要在finally中释放

tryLock避免死锁

先看下死锁的情况:

public class TestLock {
    public static void main(String[] args) {
        final Accout accout1 = new Accout(20);
        final Accout accout2 = new Accout(20);
        final TestLock testLock = new TestLock();
        new Thread(){
            @Override
            public void run() {
                testLock.transferMoney(accout2, accout1, 10);
            }
        }.start();
        testLock.transferMoney(accout1, accout2, 10);
    }

    public void transferMoney(Accout accout1, Accout accout2, int money){
        synchronized (accout1){
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (accout2){
                accout1.add(money);
                accout2.decrease(money);
                System.out.println(accout1.getAccout());
                System.out.println(accout2.getAccout());
            }
        }
    }
}

class Accout{
    public Accout(int accout){
        this.accout = accout;
    }

    public Lock lock = new ReentrantLock();
    private int accout;
    public void add(int money){
        accout += money;
    }

    public void decrease(int money){
        accout -= money;
    }

    public int getAccout() {
        return accout;
    }
}

改为不会死锁的情况:

public void transferMoney(Accout accout1, Accout accout2, int money){
        if(accout1.lock.tryLock()){
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(accout2.lock.tryLock()){
                accout1.add(money);
                accout2.decrease(money);
                System.out.println(accout1.getAccout());
                System.out.println(accout2.getAccout());
                accout2.lock.unlock();      // sould be in finally block
            }
            accout1.lock.unlock();      // sould be in finally block
        }
    }

如上其实主要是利用了tryLock当获取不到锁就返回false的特性。另外tryLock还能设置超时时间,指定时间得不到锁才返回false.

tryLock可中断锁

当代码遇到锁标记有很多种选择,获得锁,如果锁不再了等待锁,重入进该锁, 响应中断。
通常的锁在需要等待的时候并不会响应中断。
如下:

public class TestLock {

    public static void main(String[] args) {
        new TestLock().testUnInterrupt();
    }

    public void testUnInterrupt(){
        Lock lock = new ReentrantLock();
        ThreadTest t1 = new ThreadTest(lock, "t1");
        t1.start();
        ThreadTest t2 = new ThreadTest(lock, "t2");
        t2.start();
        t2.interrupt();
    }
}

class ThreadTest extends Thread{
    private Lock lock;
    public ThreadTest(Lock lock, String name){
        super(name);
        this.lock = lock;
    }
    @Override
    public void run(){
        lock.lock();
        System.out.println(getName() + "运行开始");
//        try {
//            lock.lockInterruptibly();
//        } catch (InterruptedException e) {
//            System.out.println(getName() + "锁等待被中断");
//            e.printStackTrace();
//        }
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            System.out.println(getName() + "休眠被中断");
//            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        System.out.println(getName() + "运行完成");
    }
}

使用lock()的返回结果:

t1运行开始
t2运行开始
t1运行完成
t2休眠被中断
t2运行完成

使用lockInterruptibly()的返回结果:

t1运行开始
t2运行开始
t2锁等待被中断

可见lockInterruptibly与普通锁的区别在于可以在锁等待的时候响应中断

lock和synchronized性能

在JDK6之前性能上Lock要好很多,但是JDK6之后改进了内部锁的算法,现在差不多

公平锁

ReentrantLock锁的构造器中可以选择是否公平,公平的意思就是开始执行的顺序将会按照锁等待的顺序。这样会造成性能下降,只有在对顺序敏感的时候才需要。

synchronized还是ReentrantLock
  • 性能上JDK1.6,ReentrantLock略好。
  • ReentrantLock可以支持复杂的语义。
  • ReentrantLock需要显示关闭
  • synchronized语法上更简单易用
    综上,其实应该更多的使用synchoronized,只有在有必要的时候才使用ReentrantLock
读写锁

普通的锁限制了并发性,对于数据无害的读-读操作也会出现锁竞争。这个时候可以使用读写锁
读写锁的一些特性:
- 基本功能就是有写入的时候要等写完完成才能再执行写入,如果没有写入的时候读是可以并发的
- 可降级不可以升级,就是读解锁前如果获取写锁会死锁,但是写锁中是可以获取读锁的
- 各锁单独可重入
如下:

public class TestLock {

    public static void main(String[] args) {
        final Cache cache = new Cache();
        new Thread(){
            @Override
            public void run() {
                cache.add("1");
                cache.add("2");
                cache.add("3");
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                cache.get();
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                cache.get();
            }
        }.start();
    }
}

class Cache{
    ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    List<String> datas = new ArrayList<String>();

    public void add(String data){
        rwl.writeLock().lock();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        datas.add(data);
        System.out.println("添加" + data);
        rwl.writeLock().unlock();
    }

    public String get(){
        rwl.readLock().lock();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String data = datas.get(0);
        datas.remove(0);
        rwl.readLock().unlock();
        System.out.println("获得" + data);
        return data;
    }
}

可以验证写锁互斥,读写并发。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值