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。

 

 


 

 

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