并发编程12-显示锁

简介: <div class="markdown_views"><p>内部所拥有比较好的性能,但是在灵活性方面有缺陷,并且如果申请锁失败就会陷入阻塞等待的过程中。 <br>对于一些场景,我们可以使用显示锁Lock</p><h4 id="基本应用">基本应用</h4><p>Lock 的lock方法相当于进入同步块, unlock方法相当于退出同步块。支持跟内部锁同样的重入

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

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

相关文章
|
9天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
|
4月前
|
Linux C语言
一个简单案例理解为什么在多线程的应用中要使用锁
一个简单案例理解为什么在多线程的应用中要使用锁
17 0
|
10月前
|
安全 编译器 C++
C++并发编程中的锁的介绍(二)
C++并发编程中的锁的介绍(二)
123 0
|
10月前
|
算法 安全 C++
C++并发编程中的锁的介绍(一)
C++并发编程中的锁的介绍(一)
130 0
|
10月前
|
缓存 算法 安全
06.一文看懂并发编程中的锁
大家好,我是王有志。相信你经常会听到读锁/写锁,公平锁/非公平锁,乐观锁/悲观锁等五花八门的锁,那么每种锁有什么用呢?它们又有什么区别呢?今天我们就一起聊聊并发编程中的各种锁。
97 1
06.一文看懂并发编程中的锁
|
算法 安全 Java
深入浅出理解Java并发AQS的独占锁模式
深入浅出理解Java并发AQS的独占锁模式
102 0
深入浅出理解Java并发AQS的独占锁模式
|
Java
Java并发编程学习系列一:线程与锁(二)
Java并发编程学习系列一:线程与锁(二)
54 0
Java并发编程学习系列一:线程与锁(二)