java并发编程之Lock和ReentrantLock

一. Lock,ReentrantLock介绍

了解lock之前可以对比jdk提供的synchronzied,synchronzied也被用于实现线程同步,但是有些场景下并不灵活,如多个同步方法,每次只能有一个线程访问;而Lock则可以非常灵活的在代码中实现同步机制。

Lock 接口的定义

public interface Lock {

     // 获取锁,若当前lock被其他线程获取;则此线程阻塞等待lock被释放
     // 如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁
    void lock();

    // 获取锁,若当前锁不可用(被其他线程获取);
    // 则阻塞线程,等待获取锁,则这个线程能够响应中断,即中断线程的等待状态
    void lockInterruptibly() throws InterruptedException;

    // 来尝试获取锁,如果获取成功,则返回true;
    // 如果获取失败(即锁已被其他线程获取),则返回false
    // 也就是说,这个方法无论如何都会立即返回
    boolean tryLock();

    // 在拿不到锁时会等待一定的时间
    // 等待过程中,可以被中断
    // 超过时间,依然获取不到,则返回false;否则返回true
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // 释放锁
    void unlock();

    // 返回一个绑定该lock的Condtion对象
    // 在Condition#await()之前,锁会被该线程持有
    // Condition#await() 会自动释放锁,在wait返回之后,会自动获取锁
    Condition newCondition();
}

ReentrantLock

ReentrantLock是唯一一个实现了Lock的接口的类,叫做可重入锁,意思是拥有锁之后,可以再次获取锁

二.基本使用

1.模拟上下班场景,公司要求A岗位同时只能安排一个人上班;

package com.lhy.jui.tools.lock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author: lihuiyong
 * @DATE : 2019/10/29
 * @version:1.0.0
 * @description: lock 使用场景
 *
 * 模拟保安值班,每次只能一个保安值班
 */
public class LockExample {

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

    private void workIng(){
        System.out.println(Thread.currentThread().getName() +" 上班!");
    }
    private void workOff(){
        System.out.println(Thread.currentThread().getName() +" 下班!");
    }

    public  void work(){
        try{
            lock.lock();
                workIng();
                System.out.println(Thread.currentThread().getName() +"上班中");
                Thread.sleep(100);
                workOff();
        }catch (Exception ex){
            System.out.println(Thread.currentThread().getName()+"无法上班,有人还未下班");
        }finally {
            if(lock != null){
                lock.unlock();
            }
        }
    }

    public  void workTryLock(){
        boolean b = lock.tryLock();
        System.out.println(b);
        if(b){
            try {
                workIng();
                System.out.println(Thread.currentThread().getName() +"上班中");
                workOff();
            }catch (Exception ex){
                ex.printStackTrace();
            }finally {
                if(lock != null){
                    lock.unlock();
                }
            }
        }else{
            System.out.println(Thread.currentThread().getName()+"无法上班,有人还未下班");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final LockExample  lockExample = new LockExample();
        List<Thread> threadList = new ArrayList<>(50);
        int i = 0 ;
        do {
            Thread a = new Thread(new Runnable() {
                @Override
                public void run() {
                    lockExample.work();
                }
            },"a_"+i);

            Thread b = new Thread(new Runnable() {
                @Override
                public void run() {
                    lockExample.work();
                }
            },"b_"+i);
            threadList.add(a);
            threadList.add(b);

        }while(i++ < 50);

        threadList.forEach(s->{
            s.start();
        });

        Thread.sleep(5000);
        System.out.println("main over!");
    }
}

为了效果展示,work方法 Thread.sleep(100);

效果如下 :

总结下lock的使用:

a.先创建一个lock对象,Lock lock = new ReentrantLock();

b.work方法进入,需要线程同步,开始先获取锁,若锁被其他线程占用,则阻塞

c.执行完work方法之后,unlock释放锁,释放锁和lock是成对出现的,确保unlock要在锁使用完之后及时释放,否则造成死锁;所以一般放在finally里面释放锁.

d.对比上面LockExample中的workTryLock使用的lock.tryLock();tryLock会尝试获取锁,方法有返回值;如果获取到锁,返回true;tryLock可以传等待时间,即在特定的时间内获取锁;

而lock.lock跟synchronzied一样,直接占用锁,无需返回值,其他线程进来若锁被占用,则会阻塞;

 

2.Lock和Condtion配合使用

有些场景都线程执行要顺序要求,即需要保证并发时入队和出队的要求,类似实现线程A的执行需要等待线程B执行完成才执行,

类似CountDownLatch的功能,但是它更多用于阻塞队列,生产者消费者的模式,实现等待通知机制;

我们模拟下并发线程中的有界队列

package com.lhy.jui.tools.lock;

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

/**
 * @Author: lihuiyong
 * @DATE : 2019/10/31
 * @version:1.0.0
 * @description:   lock 和 condition结合实现线程安全中的有界队列
 */
public class LockWithCondition {


    public static class BlockQuene<T> {
        /**
         * 队列
         */
        private List quene = new LinkedList<T>();
        /**
         * 队列大小
         */
        private int queneLimit;
        /**
         * lock
         */
        private Lock lock = new ReentrantLock();
        /**
         * take操作
         */
        private Condition takeCondition = lock.newCondition();
        /**
         * put操作
         */
        private Condition putCondition = lock.newCondition();

        public BlockQuene(int queneLimit) {
            this.queneLimit = queneLimit;
        }

        /**
         * 入队
         */
        public void pushQuene(T t) {
            lock.lock();
            try {
                if (quene.size() == queneLimit) {
                    //队列已满,阻塞put操作
                    putCondition.await();
                }
                quene.add(t);
                takeCondition.signal();
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                if (lock != null) {
                    lock.unlock();
                }
            }
        }

        /**
         * 出队
         */
        public T popQuene() {
            lock.lock();
            T t = null;
            try {
                if (quene.isEmpty()) {
                    //队列为空,阻塞take操作
                    takeCondition.await();
                }
                putCondition.signal();
                t = (T)quene.remove(0);

            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                if (lock != null) {
                    lock.unlock();
                }
            }
            return t;
        }
    }

    public static class ThreadPop implements Runnable{

        private BlockQuene<Integer> bolckQuene;

        public ThreadPop(BlockQuene<Integer> bolckQuene){
            this.bolckQuene = bolckQuene;
        }
        @Override
        public void run() {
            while(true){
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+" will pop.....");
                    Integer i = bolckQuene.popQuene();
                    System.out.println(" i="+i.intValue()+" alread pop");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static class ThreadPush implements Runnable{

        private BlockQuene<Integer> bolckQuene;

        public ThreadPush(BlockQuene<Integer> bolckQuene){
            this.bolckQuene = bolckQuene;
        }
        @Override
        public void run() {
           int i = 5;
           while (i> 0){
               try {
                   Thread.sleep(5000);
                   System.out.println(" i="+i+" will push");
                   bolckQuene.pushQuene(i--);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        }
    }


    public static void main(String[] args) {
        BlockQuene b = new BlockQuene(10);

        Thread threadPop = new Thread(new ThreadPop(b));
        Thread threadPush = new Thread(new ThreadPush(b));
        threadPush.start();
        threadPop.start();

    }

}

运行效果 :

Condition与Lock配套使用,通过 lock.newConditin() 进行实例化

BlockQuene 类主要两个方法,一个入列一个出列,takeCondition 和 putCondition分别用于出列和入列的阻塞;通过pushQuene和popQuene实现入出队列的操作;

当popQuene时,如果队列为空,则用takeCondition 阻塞线程,若队列不为空,则直接pop,同时通知putCondition可以push;

当pushQuene时,如果队列已满,则用putCondition阻塞线程,若队列未满,加入队列同时通知takeCondition 可以pop了;

掌握这个原理之后,我们再去了解下阻塞队列BlockingQueue就比较简单了,后续会介绍BlockingQueue。

 

 


 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章