可重入概念是不同的方法上加的是同一个锁,在一个方法中可以调用另一个方法,不会出现死锁的问题(意思就是我锁了一次之后还可以对同样这把锁再锁一次);
一、回顾一下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(); } }