JUC-ReentrantLock

可重入概念是不同的方法上加的是同一个锁,在一个方法中可以调用另一个方法,不会出现死锁的问题(意思就是我锁了一次之后还可以对同样这把锁再锁一次);

一、回顾一下synchronized锁可重入性:

记得在之前讲synchronized的时候讲过,synchronized就是可重入的。假设synchronized不可重入,字类和父类synchronized(this) ,this当然是同一把锁,在字类调用父类方法就会出现死锁。

package com.dongl.juc.juc_008;

import java.util.concurrent.TimeUnit;

/**
 * @author D-L
 * @Classname T03_ReentrantLock01
 * @Version 1.0
 * @Description 可重入锁-synchronized
 * @Date 2020/7/22
 */
public class T03_ReentrantLock01 {

    public synchronized void m1(){
        System.out.println("Method m1 is start ----------");
        for (int i = 0; i < 10; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
            if(i == 5) m2();
        }
        System.out.println("Method m1 is end ----------");
    }

    private synchronized void m2() {
        System.out.println("Method m2 is running ----------");
    }

    public static void main(String[] args) {
      T03_ReentrantLock01 t = new T03_ReentrantLock01();
      new Thread(t::m1).start();
    }
}

二、ReentrantLock新类型的锁基于CAS)可重入性

ReentrantLock可以替代synchronized,怎么替代呢?话不多说,直接看代码:

package com.dongl.juc01.juc_001;


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

/**
 * @author D-L
 * @Classname T01_ReentrantLock
 * @Version 1.0
 * @Description ReentrantLock代替synchronized 之前写synchronized地方换成lock.lock();
 *                lock加完锁之后,需要手动释放 synchronized加锁出现异常时 jvm会自动释放锁
 *                但是lock不会 如果出现异常没有手动释放就会出现死锁  try{}catch{}finally{lock.unlock} 手动释放
 * @Date 2020/7/22
 */
public class T01_ReentrantLock {
    Lock lock = new ReentrantLock();

    /**方法m1**/
    public void m1(){
        System.out.println("Method m1 is start ----------");
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
                if (i == 5) m2();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //这里一定要手动解锁 synchronized出现异常时jvm会自动释放锁  但是lock必须手动释放
            lock.unlock();
        }
        System.out.println("Method m1 is end ----------");
    }

    /**方法m2**/
    public void m2(){
        try {
            lock.lock();
            System.out.println("Method m2 is running ----------");
        } finally {
            //手动释放锁
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        T01_ReentrantLock t = new T01_ReentrantLock();
        new Thread(t::m1).start();
    }
}

有人会问既然reentrantlock和synchronized差不多问什么还要使用呢? 那当然时reentrantlock肯定有一些比synchronized强大的功能 ,锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等synchronized关键字所不具备的同步特性。;

 

三、下面就来看一看reentrantlock有什么特殊功能:

  • lock.tryLock(3,TimeUnit.SECONDS); 获取锁以及超时获取锁
  • lock.lockInterruptibly(); 中断

1、lock.tryLock(3,TimeUnit.SECONDS);

线程在调用lock方法来获得另一个线程所持有的锁的时候,很可能发生阻塞。应该更加谨慎地申请锁。tryLock 是防止自锁的一个重要方式 ,tryLock方法试图申请一个锁,在成功获得锁后返回true,否则,立即返回false,而且线程可以立即离开去做其他事。

可以调用tryLock时,使用超时参数。
lock方法不能被中断。如果一个线程在等待获得一个锁时被中断,中断线程在获得锁之前一直处于阻塞状态。如果出现死锁,那么,lock方法就无法终止。

package com.dongl.juc01.juc_001;


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

/**
 * @author D-L
 * @Classname T01_ReentrantLock
 * @Version 1.0
 * @Description ReentrantLock
 *
 * @Date 2020/7/22
 */
public class T02_ReentrantLock {
    Lock lock = new ReentrantLock();

    /**方法m1**/
    public void m1(){
        System.out.println("Method m1 is start ----------");
        try {
            lock.lock();
            for (int i = 0; i <10; i++) {
                System.out.println(i);
            }
            TimeUnit.SECONDS.sleep(5);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //这里一定要手动解锁 synchronized出现异常时jvm会自动释放锁  但是lock必须手动释放
            lock.unlock();
        }
        System.out.println("Method m1 is end ----------");
    }

    /**方法m2**/
    public void m2(){
        boolean locked = false;

        try {
            locked = lock.tryLock(10,TimeUnit.SECONDS);
            if(locked == true) System.out.println("Method m2 is running ----------");
            if(locked == false) System.out.println(Thread.currentThread().getName()+"获取到不到锁------------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(locked) lock.unlock();
        }
    }
public static void main(String[] args) { T02_ReentrantLock t = new T02_ReentrantLock(); new Thread(t::m1 ,"Thread m1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(t::m2,"Thread m2").start(); } }

 2、lock.lock(); lock.tryLock(5 , TimeUnit.SECONDS);lock.lockInterruptibly(); 响应interrupt()的区别:

  • lock 优先考虑获取锁,待获取锁成功后,才响应中断。
  • tryLock 尝试获得锁 在规定时间获取不到,就会抛出异常IllegalMonitorStateException。
  • lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。

ReentrantLock.lockInterruptibly允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,如果未获得锁会抛出一个InterruptedException异常ReentrantLock.lock方法在线程未获得锁之前不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程。

package com.dongl.juc01.juc_001;

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

/**
 * @author D-L
 * @Classname T03_ReentrantLock
 * @Version 1.0
 * @Description ReentrantLock
 *               lock.lock(); lock.tryLock(5 , TimeUnit.SECONDS);lock.lockInterruptibly();响应interrupt();的区别
 * @Date 2020/7/23
 */
public class T03_ReentrantLock {
    final Lock lock = new ReentrantLock();
    public void m1() {
        System.out.println(Thread.currentThread().getName()+" start --------------------");
        try {
            lock.lock();
            TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);  //睡眠时间足够长
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+" interrupted.");
        } finally {
            lock.unlock();
        }
        System.out.println(Thread.currentThread().getName()+" end ----------------------");
    }

    public void m2() {
        System.out.println(Thread.currentThread().getName()+" start --------------------");
        try {
//            lock.lock(); //优先获得锁 获得锁之后会响应中断
//            lock.tryLock(5 , TimeUnit.SECONDS);  //尝试获得锁 在规定时间获取不到,就会抛出异常IllegalMonitorStateException
            lock.lockInterruptibly(); //可被终止 无论获没获取到锁 优先响应中断

            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+" interrupted.");
        } finally {
            lock.unlock();
        }
        System.out.println(Thread.currentThread().getName()+" end ----------------------");
    }
    public static void main(String[] args) throws InterruptedException {
        T03_ReentrantLock t05 = new T03_ReentrantLock();
        Thread t1 = new Thread(t05::m1 ,"m1");
        Thread t2 = new Thread(t05::m2 ,"m2");
        //如果是m1先启动 m1获得锁之后要睡很久 m2很难在短时间里获得锁 不过lock.lockInterruptibly();优先考虑响应中断,
        // 而不是响应锁的普通获取或重入获取,所以此时抛出异常。
        t1.start();
        TimeUnit.SECONDS.sleep(1); //确保启动的优先级
        t2.start();
        //如果是m2先于m1获得锁 因为m1用的是lock.lock(); 优先获取锁成功后,才响应中断,如果获取不到就会一直等,
        // 直到获取锁才能响应中断 这里因为m2也睡了足够长时间 所以会一直等待 不会响应中断
//        t2.start();
//        TimeUnit.SECONDS.sleep(1);
//        t1.start();

        TimeUnit.SECONDS.sleep(2);
        t2.interrupt();
//        t1.interrupt();
    }
}

四、ReentrantLock自定义锁的公平性

ReentrantLock可以自定义锁的公平性,意思就是可以通过传参(true  false)的方式来控制,ReentrantLock默认是非公平的,公平与非公平锁的队列都基于内部维护一个双向链表,首先上来先判断当前这把锁有多少线程持有(getState() 因为这里有锁的可重入的概念,锁重入一次state+=1)。如果state==0,说明此锁无线程持有,再看等待队列是否有等待线程,如果无等待线程,修改currentThread为独占线程,state+1;如果有等待线程,要看当前锁是否为公平锁,是公平锁,新建一个node放入队列队尾,等待cpu调度,非公平锁,上来直接尝试获取这把锁;

公平锁:按照线程等待顺序获取锁,一般将获取锁失败的线程放入等待队列中,每次从FIFO队列的队头取出线程获取锁。这种方式会造成性能低下,大量的时间花费                在线程调度上。

非公平锁:不管等待顺序,每个线程获取锁的概率都是相等的,优点是提高了响应速度,不用把大量时间花费在线程调度上,而是花费在执行代码上。

package com.dongl.juc01.juc_001;


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

/**
 * @author D-L
 * @Classname T03_ReentrantLock
 * @Version 1.0
 * @Description ReentrantLock  可指定公平与非公平锁  当设置位true时 此时为公平锁 也就是线程1和线程2会按顺序执行
 *                               当参数设置为false时 此时为非公平锁,线程会出现交叉执行
 * @Date 2020/7/23
 */
public class T04_ReentrantLock extends Thread{
    //定义ReentrantLock的公平与非公平
//    final ReentrantLock lock = new ReentrantLock(true);
    final ReentrantLock lock = new ReentrantLock(false);


    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            lock.lock();
            try {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"  required the lock ----------" + i);
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args){
        T04_ReentrantLock t04 = new T04_ReentrantLock();
        Thread t1 = new Thread(t04);
        Thread t2 = new Thread(t04);
        t1.start(); t2.start();
    }
}

猜你喜欢

转载自www.cnblogs.com/dongl961230/p/13361536.html
今日推荐