java并发学习01 --- Reentrantlock 和 Condition

时间:2021-06-03 21:00:45

本文是笔者看了《实战java高并发程序设计》之后加上自己的理解所写的笔记。

之所以直接从并发工具开始,是因为多线程的基础知识,例如多线程创建,常用的方法,以及synchronized,volatile关键字等知识之前学习的时候已经学习过许多遍了,但是java并发包却鲜有接触,这次决定写成博客,系列的总结一下。(之后有时间的话再把前面的知识补充上来,让这个系列更完整一些)

 

Reentrantlock(重入锁) 

简单看一下它的用法:

重入锁基本代码演示:

package thread.thread_util;

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

/**
 * 展示重入锁的用法
 */
public class Lesson16_ReetrantLock {
    public static void main(String[] args) {
        Runnable target = new Runnable() {
            private Lock lock = new ReentrantLock();
            @Override
            public void run() {
                lock.lock();
//                lock.lock();
                try {
                    for (int i = 0; i < 5 ; i++) {
                        System.out.println(Thread.currentThread().getId() + ": " + i);
                    }
                } finally {
                    lock.unlock();
//                    lock.unlock();
                }
            }
        };
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        t1.start();
        t2.start();
    }
}

 

重入到底是什么意思?

相比synchronized关键字,重入锁显示的调用了加锁和解锁的时机,但是为什么要叫重入锁呢?重入二字是什么意思呢?

重入的意思就是同一个线程可以反复进入同一个锁,上面的代码中就算把注释给去掉代码也是可以执行的。

当然这个代码例子仍然不够贴切,谁会没事加两把锁上去呢?但是你可以想象一下递归,我们都知道递归的每次操作都把相关的变量压入了一个栈之中,执行完成就弹出栈,然后再执行上一层函数,这里如果我们进行了加锁操作的话,那么每一层函数在递推的时候就都会加上一层锁,而在回归的时候函数结束,则会释放锁。重入锁的内部有一个计数器,每加一个锁,计数器就加1,释放一个锁,计数器就减1,只有当计数器为0时,锁才能被释放。

 

我们以经典的斐波那契数作为例子来展示一下。

深入理解“重入”代码演示:

package thread.thread_util;

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

/**
 * 递归中的重入锁
 */
public class Lesson16_ReentrantLock02 {
    public static void main(String[] args) {
        Runnable target = new Runnable() {
            private Lock lock = new ReentrantLock();
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getId());
                System.out.println("最终结果为: " + fibonacci(3));
            }

            public int fibonacci (int n) {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getId() + "获得了锁");
                    if( n == 1 ) return 1;
                    if( n == 2 ) return 1;
                    return fibonacci(n-1) + fibonacci(n-2);
                } finally {
                    lock.unlock();
                    System.out.println(Thread.currentThread().getId() + "释放了锁");
                }
            }
        };
        Thread thread = new Thread(target);
        thread.start();
    }
}

结果:

11
11获得了锁
11获得了锁
11释放了锁
11获得了锁
11释放了锁
11释放了锁
最终结果为: 2

我们可以看到这里总共获得了3次锁,释放了3次锁,整个锁才会被最终释放。

你可以再开一个线程进行验证,结果会类似下面这样:

11
11获得了锁
11获得了锁
11释放了锁
11获得了锁
11释放了锁
11释放了锁
最终结果为: 2
12
12获得了锁
12获得了锁
12释放了锁
12获得了锁
12释放了锁
12释放了锁
最终结果为: 2

 

重入锁的好基友:Condition对象

 Condition的功能和Object.wait()与Object.notify()比较相似,它让我们可以控制某个线程什么时候开始等待,什么时候被唤醒,而不是由jvm来决定。

 条件对象代码演示:

package thread.thread_util;

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

/**
 * 展示条件对象的基本用法
 * await
 * signal
 * signalAll
 */
public class Lesson17_Condition01 implements Runnable{
    public static Lock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();
    @Override
    public void run() {
        try {
            lock.lock();
            condition.await();
            System.out.println("Thread is going on");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws Exception{
        Runnable target = new Lesson17_Condition01();
        Thread thread = new Thread(target);
        thread.start();
        Thread.sleep(2000);

        lock.lock();
        condition.signal();
        lock.unlock();
    }
}

这里在Runnable对象中,将对象锁住并让线程进入了休眠状态

主线程中进行了2秒钟的休眠,这2秒钟内子线程也无法执行,因为被“await()”了,直到2秒钟后,主线程再次开始执行,对线程进行了唤醒(signal)操作,唤醒操作也要进行加锁操作。

 

 

关于重入锁还有几点需要说明:

  • 中断响应:对于synchronized来说,线程只有两种状态,要么获得锁开始执行,要么在队列中等待。

          而对于重入锁来说,还存在另外一种状态,那就是中断线程,也就是把等待队列中的线程中断,那么这个中断有什么用呢?

          比如说如果出现了死锁的情况,两个线程都在等待对方释放资源,那么这个时候如果我主动的中断一个线程,那么这个线程所持有的资源就被释放啦。我们来看看代码

 重入锁中断响应代码演示:

package thread.thread_util;

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

/**
 * 展示重入锁的中断响应功能
 * 首先构造一个死锁
 * 然后中断某个线程,释放资源
 * 最后另外一个线程就可以获得释放的资源,完成线程的任务
 */
public class Lesson16_ReentrantLock03 implements Runnable{
    private static ReentrantLock lock1 = new ReentrantLock();
    private static ReentrantLock lock2 = new ReentrantLock();
    private int flag = 0;

    public void setFlag(int val) {
        this.flag = val;
    }
    @Override
    public void run() {

        try {
            if(flag == 1) {
                lock1.lockInterruptibly();
                Thread.sleep(1000);
                lock2.lockInterruptibly();
                System.out.println("Thread1 is going on ");
            }
            else {
                lock2.lockInterruptibly();
                Thread.sleep(1000);
                lock1.lockInterruptibly();
                System.out.println("Thread2 is going on ");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(lock1.isHeldByCurrentThread()){
                lock1.unlock();
            }
            if(lock2.isHeldByCurrentThread()){
                lock2.unlock();
            }
        }
    }

    public static void main(String[] args) {
        Lesson16_ReentrantLock03 target1 = new Lesson16_ReentrantLock03();
        Lesson16_ReentrantLock03 target2 = new Lesson16_ReentrantLock03();

        target1.setFlag(1);
        target2.setFlag(2);

        Thread thread1 = new Thread(target1);
        Thread thread2 = new Thread(target2);
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }
}

 

  • lockInterruptibly  : 虽然锁住了对象,但是可以响应中断,发现中断之后就释放锁

       Acquires the lock unless the current thread is interrupted. (文档说明)

最后线程2进行了中断,代码中的“else”里面的代码中的lockInterruptibly()方法就会使线程2 释放lock2并放弃对lock1的请求。

运行结果:

Thread1 is going on 
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
    at thread.thread_util.Lesson16_ReentrantLock03.run(Lesson16_ReentrantLock03.java:33)
    at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

 

 

锁申请等待时限(tryLock)

这是主动的中断,还有另外一种更为简便的方法,就是给锁的申请添加时限,如果规定时间内我还没得到锁,那我就不要这把锁了

trylock() 代码演示:

package thread.thread_util;

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

public class Lesson16_ReentrantLock04 implements Runnable{
    private static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run () {
        try {
            if(lock.tryLock(3,TimeUnit.SECONDS)){
                System.out.println(Thread.currentThread().getName() + " get lock successful");
                Thread.sleep(4000);
            } else {
                System.out.println(Thread.currentThread().getName() + " get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Lesson16_ReentrantLock04 target = new Lesson16_ReentrantLock04();
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}

这里线程1先获得了锁,然后睡眠6秒钟(sleep并不会释放锁),所以线程2在3秒钟内都无法获得所最后进入else代码段。

最后结果:

线程1 get lock successful
线程2 get lock failed

 

 

关于重入锁和条件对象暂时就写到这里。