ReentrantLock入门级

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JavaMrZhang/article/details/89387354

是什么

synchronized关键词功能一致,让线程保持同步。synchronized是基于Java语法上的实现,而ReentrantLock是基于API实现,操作上相对灵活。JDK1.7 之后 两者的性能上不分秋色。ReentrantLock实现了Lock接口,我们来看看那Lock几个主要接口定义:

/**
当前线程尝试获取独占锁,如果发现锁已被其他线程占用将被挂起。
**/
void lock();

/**
同void lock() 方法类似,只不过该方法能够被线程中断。
**/
void lockInterruptibly() throws InterruptedException;

/**
当前线程调用该方法会尝试获取锁,如果获取到返回true,反之返回false,并不会阻塞该线程。
**/
boolean tryLock();
/**
同boolean tryLock()方法类似。也是尝试获取锁,在拿不到锁时会等待一定时间,等待时可以被中断。超时之后还未拿到锁返回false.
**/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

/**
 释放锁。
**/
void unlock();

/**
 返回一个与lock实例绑定的Condition,通过调用Condition#await()方法,可以释放当前线程持有的锁并阻塞挂起。
**/
Condition newCondition();

Condition的作用主要应用于多线程的协同,下面看看Condition 接口的一些主要定义:

/**
 对于获取锁的线程,调用此方法后会阻塞直到其他线程唤醒或者线程被中断
**/
void await() throws InterruptedException;

/**
相比较await()而言,它不响应中断。
**/
void awaitUninterruptibly();
/**
唤醒因调用同一个condition实例的await() 方法 而阻塞挂起的线程
**/ 
void signal();

/**
 唤醒所有阻塞的线程
**/
void signalAll();

怎么用

ReentrantLock锁的基本使用

需求:有20个用户(线程)同时去抢购剩余的10张火车票,必须保证数据不出现混乱。具体实现如下:

TicketMachine

/**
 * 取票机
 */
public class TicketMachine {
    Lock lock = new ReentrantLock();
    /**
     * 票数量
     */
    private Integer ticketNum = 10;

    /**
     * 购买
     */
    public void buying() {
        try {
           // 1.
            lock.lock();
            if (ticketNum > 0) {
                System.out.println(Thread.currentThread().getName() + "购买了第" + ticketNum + "张票");

                ticketNum--;
            } else {
                System.out.println("已购买完,抢不到了.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
           // 2.
            lock.unlock();
        }
    }
}

运行方法

 TicketMachine ticketMachine = new TicketMachine();
        for (Integer i = 0; i < 20; i++) {
            new Thread(() -> {
                ticketMachine.buying();
            }, "同学" + (i + 1)).start();
        }

运行结果

同学1购买了第10张票
同学4购买了第9张票
同学2购买了第8张票
同学3购买了第7张票
同学5购买了第6张票
同学6购买了第5张票
同学7购买了第4张票
同学8购买了第3张票
同学9购买了第2张票
同学10购买了第1张票
已购买完,抢不到了.
已购买完,抢不到了.
已购买完,抢不到了.
已购买完,抢不到了.
已购买完,抢不到了.
已购买完,抢不到了.

上面有几处我们着重的说一下 。代码(1)处调用lock() 方法获取锁保证同一时刻只能有一个线程对变量ticketNum(票数)进行操作,不会造成脏数据的问题。代码(2)处将unlock()方法放置finally块中 确保代码执行完毕后锁能进行释放,不造成死锁。

针对此案例我们提出几个疑问

  • 若没有调用lock()方法,直接调用 unLock() 方法会出现问题么?
  • 若多次调用lock()方法,仅调用一次 unLock() 方法会出现问题么?

问题一:若无调用lock() 方法,直接执行unLock() 方法将会抛出IllegalMonitorStateException。
问题二: 会导致其他线程获取不到锁。

Lock结合Condition 的使用

需求:10个线程每次以10增量值对变量sum进行求和运算,当任何一个线程发现求和运算得出的结果等于100,将唤醒另一个线程将结果输出。

具体实现如下:

/**
 * Lock 和 Condition 配合使用案例
 */
public class LockConditionDemo {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    /**
     * 总和
     */
    private Integer sum = 0;

    /**
     * 计算和
     *
     * @param count
     */
    public void calcSum(Integer count) {
        try {
            lock.lock();
            sum += count;
            //4.
            if (sum == 100) {
                condition.signal();
                System.out.println("sum 已等于 100 ,唤醒线程...");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    /**
     * 显示总和
     */
    public void show() {
        try {
            lock.lock();
            System.out.println("线程【"+Thread.currentThread().getName()+"】"+"等待输出sum和");
            //2.
            condition.await();

            System.out.println("线程【"+Thread.currentThread().getName()+"】"+"得出 sum = " + sum);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

运行方法:

    public static void main(String[] args) {
        LockConditionDemo t = new LockConditionDemo();
        //1.
        Thread thread = new Thread(() -> {
            t.show();
        }, "获取求和线程");
        thread.start();

        //3.
        for (Integer i = 0; i < 10; i++) {
            new Thread(() -> t.calcSum(10), "线程" + i).start();
        }

    }

输出结果:

线程【获取求和线程】等待输出sum和
sum 已等于 100 ,唤醒线程...
线程【获取求和线程】得出 sum = 100

从代码(1) 处,我们可以看出启动了一个 获取求和线程,代码(2) 处,调用了condition.await()方法,将该线程阻塞挂起,等待其他线程唤醒,代码(3) 处启动了20个 对变量sum求和的线程,紧接着代码(4)处对sum的值进行判断,如果等于100将调用 condition.signal(),唤醒因调用同一个condition实例的await() 方法 而阻塞挂起的线程。


清山绿水始于尘,博学多识贵于勤。
我有酒,你有故事吗?
微信公众号:「Java锦囊」。
欢迎一起谈天说地,聊Java。


书写技术文章是一个循序渐进的过程,所以我不能保证每句话、每行代码都是对的,但至少能保证不复制、不粘贴,每篇文章都是自己对技术的认识、细心斟酌总结出来的。乔布斯说:我们在这个星球上的时间都很短,很少有机会去做几件真正伟大的事情,同时要做得好,我必须要趁我还年轻的时候完成这些事。
其实我想说的是,我是一枚程序员,我只想在有限的时间内尽可能去沉淀我这一生中所能沉淀下来的东西

猜你喜欢

转载自blog.csdn.net/JavaMrZhang/article/details/89387354