ReetrantLock属于可重入锁,Synchronized本身也是可重入锁。
什么是可重入锁?简单的意思就是我锁了一下,还可以对同样这把锁再锁一下。
比如说,有一个方法m1是sync的,在方法里面做了一个循环每次睡一秒,每隔一秒打印一下,接下来调用方法m2,m2也是一个sync的方法。
分析一下,如果sync是不可重入锁的话,会是什么情况?第一个线程申请这把锁,锁的这个对象,然后这里如果是第二个线程来进行申请的话,他start不了,必须要等第一个线程结束了。因为这两个是不同的线程。两个线程之间肯定要争用的,可以在m1里面调用m2就可以,sync方法是可以调用sync方法的。
synchronized void m1() { for (int i = 0; i < 10; i++) { try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } System.out.println(i); if(i==2) m2(); } } synchronized void m2() { System.out.println("m2..."); } public static void main(String[] args) { ReentrantLock02 lock02=new ReentrantLock02(); //重入锁验证 new Thread(lock02::m1).start(); }
从代码运行结果中可以看出,sync属于重入锁。ReentrantLock是可以替代synchronized的,将以上代码的synchronized位置换成lock.lock();加完锁之后,还要记得解锁。
使用try...finally...,在finally里面一定要进行解锁处理lock.unlock()。
##但是既然ReentrantLock和Synchronized差不多的话,为什么要用它呢?
既然ReentrantLock出现,那一定是有比Synchronized强大的地方,你可以使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行;synchronized如果搞不定的话,他就会阻塞了,但是用ReentrantLock你自己就可以决定要不要wait。
//使用tryLock()代替lock.lock(),实现加锁的功能
Lock lock=new ReentrantLock(); private void m() { boolean locked=false; try { locked=lock.tryLock(); if(locked) { for (int i = 0; i < 10; i++) { TimeUnit.SECONDS.sleep(1); System.out.println(i); } } } catch (Exception e) { e.printStackTrace(); }finally { if(locked) lock.unlock(); } } public void m2(){
boolean locked=false; try {
//由于上一个线程执行时间超出5s,所以,得不到这把锁,会跳过继续执行后面的代码
//如果将时间改成大于10s,则可以正常获取锁并输出结果
locked=lock.tryLock(5, TimeUnit.SECONDS); if(locked) System.out.println("m2....."+locked); } catch (Exception e) { e.printStackTrace(); }finally {
if(locked)
lock.unlock(); } }public static void main(String[] args) { ReentrantLockTest01 test01=new ReentrantLockTest01(); new Thread(test01::m).start(); try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } new Thread(test01::m2).start();
}
除了比synchronized多的这个功能之外,ReentrantLock()还可以用lock.lockInterruptibly()这个类,对interrupt()方法做出响应,表示可以被打断的加锁,如果以这种方式加锁的话,我们可以调用一个t2.interrupt();打断线程t2的等待。比如说一个线程执行过程中,内部进行了sleep操作,一直沉睡,而另外一个线程使用的是lock.lock(),他会一直在那儿等待,是无法打不断的。如果我们的2线程使用的是lock.lockInterruptibly()这个类可以被打断的,当你要想停止线程2就可以用Interrupt()。
public static void main(String[] args) { Lock lock=new ReentrantLock(); //线程1中拿到锁做了无限时间睡眠的操作,这样线程2会拿不到,一直处于等待。 Thread t1=new Thread(()->{ try { lock.lock(); System.out.println("t1 start"); TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); System.err.println("t1 end"); } catch (InterruptedException e) { System.out.println("interrupted"); }finally { lock.unlock(); } }); t1.start(); Thread t2=new Thread(()->{ try { //lock.lock(); /* * 方法lockInterruptibly()可以对interrupt()方法做出响应,不再进行等待,释放资源 * 然后此处会捕获线程被打断的异常 */ lock.lockInterruptibly(); System.out.println("t2 start"); TimeUnit.SECONDS.sleep(5); System.out.println("t2 end"); } catch (InterruptedException e) { System.out.println("t2 interrupted"); }finally { lock.unlock(); } }); t2.start(); try { for (int i = 0; i < 5; i++) { System.out.println(i); TimeUnit.SECONDS.sleep(1); } } catch (InterruptedException e) { e.printStackTrace(); }
//打断线程2的等待 t2.interrupt(); }
##ReentrantLock还可以表示公平锁,就是new的ReentrantLock()时,在括号中加入true。
顾名思义,既然是公平锁,那一定是很公平的。取消了后来居上的情况,所有的线程都是遵循着先到者先行的规则。简单的说就是先等待锁的先执行(排队)。
如果说这个锁是公平锁,来了一个线程想要执行的话,这个线程会先检查队列里有没有在它前面等待的,如果有的话,他就先进队列里等着别人先执行。
ReentrantLock默认属于非公平锁。
//公平锁,锁一定会分给下一排队等待的线程,不会出现抢锁插队现象 //去除true或者改成false后,锁就变成了非公平锁,执行完全靠抢锁。 private static ReentrantLock lock=new ReentrantLock(true); public void run(){ for (int i = 0; i < 100; i++) { lock.lock(); try { System.err.println(Thread.currentThread().getName()+"得到锁"); } finally { lock.unlock(); } } } public static void main(String[] args) { ReentrantLock04 lock04=new ReentrantLock04(); Thread thread01=new Thread(lock04); Thread thread02=new Thread(lock04); thread01.start(); thread02.start(); }
现在除了synchronized之外,大多数都是cas。但是谈到AQS的话,它的内部也是cas的,只不过做了一个比较隐蔽的锁升级。