zoukankan      html  css  js  c++  java
  • 17_重入锁ReentrantLock

    【概述】

    重入锁可以完全代替synchronized关键字。

    与synchronized相比,重入锁ReentrantLock有着显示的操作过程,即开发人员必须手动指定何时加锁,何时释放锁,所以重入锁对于逻辑控制的灵活性好于synchronized。

    要注意的是,每次在退出临界区时,必须记得释放锁,否则其他线程将没有机会访问临界区了。

    【ReentrantLock入门例子】

    package com.higgin.reentrantLock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * Created by HigginCui on 2017/5/15.
     */
    public class ReentrantLockThread implements Runnable{
    
        public static ReentrantLock lock = new ReentrantLock();  //实例化一个重入锁
    
        public static int num = 0;
    
        @Override
        public void run() {
            for(int i =0; i<100; i++){
                lock.lock();   //上锁
                try{
                    num++;
                }finally {
                    lock.unlock();  //释放锁
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException{
            ReentrantLockThread rlThread = new ReentrantLockThread();
            Thread t1 = new Thread(rlThread);
            Thread t2 = new Thread(rlThread);
    
            t1.start();
            t2.start();
    
            t1.join();
            t2.join();
    
            System.out.println(num);
        }
    }

    【运行结果】

    【为什么叫重入锁】

    对于一个线程,这种锁是可以重复进入的。一个线程可以两次获得同一个锁。

    有一点要注意,如果一个线程多次获得锁,那么释放锁必须有相同的次数。

    如果释放锁的次数多了,会抛出一个java.lang.IllegalMonitorStateException异常。

    如果释放锁的次数少了,相当于还持有这个锁,其他线程无法进入临界区。

    lock.lock();
    lock.lock();  //一个线程可以多次获得同一个锁
    try{
        num++;
    }finally {
        lock.unlock();
        lock.unlock();  //锁必须释放相同的次数
    }

    【ReentrantLock的几个重要方法】

    1.lock():获得锁,如果锁被占用,则等待。

    2.lockInterruptibly():获得锁,但会优先响应中断。

    3.tryLock():尝试获得锁,返回true/false,该方法不等待,立即返回。

    4.tryLock(long time, Timeunit unit):在给定的时间内尝试获得锁。

    5.unlock():释放锁。

    【中断响应】

    对于synchronized来说,如果一个线程在等待wait,那么结果只有两种情况:1.获得这把锁继续执行;2.继续保持等待。

    对于重入锁,提供了第3种可能:3.线程还可以被中断。

     

    【利用重入锁的中断响应来中断结束线程,解决死锁问题】

    package com.higgin.reentrantLock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * Created by HigginCui on 2017/5/16.
     */
    public class DeadLockThread implements Runnable{
    
        public static ReentrantLock lock1 = new ReentrantLock();
    
        public static ReentrantLock lock2 = new ReentrantLock();
    
        boolean flag;
    
        public DeadLockThread(boolean flag){
            this.flag = flag;
        }
    
        @Override
        public void run() {
            try {
                if (flag == false) {
                    lock1.lockInterruptibly(); //获得lock1锁,优先响应中断,即在等待的过程中,可以响应中断
                    Thread.sleep(500);
                    lock2.lockInterruptibly();
                } else {
                    lock2.lockInterruptibly(); //获得lcok2锁,优先响应中断,顺序与上面的相反
                    Thread.sleep(500);
                    lock1.lockInterruptibly();
                }
            } catch (InterruptedException e) {
    //            e.printStackTrace();
                System.out.println(Thread.currentThread().getName()+"被中断啦!");
            } finally {
                if (lock1.isHeldByCurrentThread()) {  //判断当前线程是否持有lock1锁
                    lock1.unlock();                 //如果持有lock1锁,就释放
                }
                if(lock2.isHeldByCurrentThread()){  //再判断当前线程是否持有lock2锁,如果持有就释放
                    lock2.unlock();
                }
                System.out.println(Thread.currentThread().getName()+"线程退出啦!!");
            }
        }
    
    
        public static void main(String[] args) throws InterruptedException{
            DeadLockThread d1 = new DeadLockThread(true);
            DeadLockThread d2 = new DeadLockThread(false);
    
            Thread t1 = new Thread(d1,"t1");
            Thread t2 = new Thread(d2,"t2");
    
            t1.start();
            t2.start();  //同时启动这两个线程,必定会产生死锁
    
            Thread.sleep(5000);  //main线程延时5秒
            t2.interrupt();  //为t2线程产生一个中断,如果没有这个中断,两个线程都会处于死锁状态,都在等待对方释放锁
        }
    }

    【运行结果】

    【分析】

    线程t1启动了,然后启动t2,

    t1先占用lock1,等待500ms,

    t2先占用lock2,等待500ms,

    t1在500ms等待结束后,想获得lock2,但此时lock2已经被t2占用,

    反之t2在500ms等待结束后,想获得lock1,但此时lcok1又被t1占用,

    于是陷入了死锁状态。

    由于lock.lcokInterruptibly()是一个可以对中断进行相应的锁申请动作,在线程等待的过程中,可以响应中断。

    main线程在5000ms后中断了t2线程,那么t2会进入catch捕获异常,打印相关的异常信息,然后进入finally代码块释放当前线程t2持有的锁lock2,t2退出。

    此时t1就可以获得t2释放的lock2,然后也进入finally代码块,释放其持有的两个锁lock1和lock2。

    【申请锁设置等待时间tryLock(10,TimeUnit.SECONDS)】

    这也是避免死锁的一种方法,即申请锁限时等待,给定一个等待时间,如果线程在这段时间内无法申请获得锁,那么线程会自动放弃。

    【tryLock(long time, Timeunit unit)的例子】

    package com.higgin.reentrantLock;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * Created by HigginCui on 2017/5/17.
     */
    public class TimeLockThread implements  Runnable {
    
        ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            try{
                if (lock.tryLock(5, TimeUnit.SECONDS)){  //如果当前线程5秒内无法获得对应的锁,那么其会自动释放锁
                    System.out.println(Thread.currentThread().getName()+"获得锁,并占用6秒!");
                    Thread.sleep(6000);  //一旦获得锁,那么回占用锁的时间为6秒
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                if(lock.isHeldByCurrentThread()){
                    System.out.println(Thread.currentThread().getName()+"释放锁啦,然后退出!");
                    lock.unlock();
                }else{
                    System.out.println(Thread.currentThread().getName()+"没有占用锁,直接退出!");
                }
            }
        }
    
        public static void main(String[] args) {
            TimeLockThread tt = new TimeLockThread();
            Thread t1 = new Thread(tt,"t1");
            Thread t2 = new Thread(tt,"t2");
    
            t1.start();
            t2.start();
        }
    }

    【运行结果】

     【分析】

    本例中,占用锁的线程会持有锁6秒的时间,故另一个线程无法在5秒内获得锁,因此请求锁会失败。

    如果tryLock( )不带参数,当前线程会尝试获得锁,如果锁并未被其他线程占用,则申请锁会成功,并立即返回true。

    如果锁被其他线程占用,则当前线程不会进行等待,而是立即返回false。

    tryLock()这种方式不会引起线程等待,因此也不会产生死锁。

    【公平锁】

    public ReentrantLock( boolean fair ); //fair=true,公平锁

    公平锁会按照时间的顺序,保证先到者先获得锁,后到者后获得锁。公平锁最大的一个特点就是:不会产生饥饿现象。只要你排队,最终还是可以等到锁的。(如果使用synchronized关键字进行锁控制,那么产生的锁就是非公平的)

    公平锁看起来很优美,但是其性能也非常低下,因此默认情况下,锁是非公平的,若没有特殊的需求,也不要使用公平锁。

  • 相关阅读:
    《PS技巧精华全部在这里!》
    c#的dictionary为什么在扩容时会以素数扩容
    初识Lua
    常用的排序
    像gal一样讲故事~
    打枪的实现
    武器的方向,人物和武器的翻转
    虚拟摇杆的修改
    实现虚拟摇杆的移动
    itemPool和MessageCenter
  • 原文地址:https://www.cnblogs.com/HigginCui/p/6859112.html
Copyright © 2011-2022 走看看