可重入锁的概念
可重入:某个线程(Thread-A)已经获取到某个锁(lock-A),在该线程(Thread-A)未解锁前又再次获取到此锁(lock-A)而不出现死锁。
可重入锁:能被某个线程在未解锁前重复获取而不出现死锁现象的锁。
可重入锁的例子
synchronized
示例代码:
public class ReentryLock1 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { int i = 1; synchronized (this) { System.out.println("第" + i + "次获取锁,这个锁是:" + this); for (i = 2; i < 10; i++) { synchronized (this) { System.out.println("第" + i + "次获取锁,这个锁是:" + this); } } } } }).start(); } }
结果:可正常运行,无死锁现象,且锁对象是同一个
ReentrantLock
示例代码:
public class ReentryLock2 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); new Thread(new Runnable() { @Override public void run() { try { int i = 1; lock.lock(); System.out.println("第" + i + "次获取锁,这个锁是:" + lock); for (i = 2; i < 10; i++) { try { lock.lock(); System.out.println("第" + i + "次获取锁,这个锁是:" + lock); } finally { lock.unlock(); } } } finally { lock.unlock(); } } }).start(); } }
结果:可正常运行,无死锁现象,且锁对象是同一个
Synchronized和ReentrantLock 对比
1、性质不同:Synchronized是关键字,它的‘锁’是Java内置的功能;而ReentrantLock是一个实现Lock接口的类,需要调用方法lock()和unlock(),配合try/finally语句块来实现‘锁’功能;
2、作用域不同:
Synchronized
修饰一个类:其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象;
修饰一个方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
修饰一个静态的方法:其作用的范围是整个方法,作用的对象是这个类的所有对象;
修饰一个代码块:被修饰的代码块称为同步语句块,其作用范围是大括号{}括起来的代码块,作用的对象是调用这个代码块的对象。
ReentrantLock
修饰代码块:以方法lock()开始,以unlock()结束。
3、结束方式不同:Synchronized一般是修饰的方法和代码块执行完以后才解锁,而ReentrantLock的结束取决于什么时候调用unlock()方法。
4、性能不同:JDK6以前Synchronized还是重量级锁,性能很差,虽然从JDK6开始对Synchronized进行性能上的优化,但是高并发的情况下还是比ReentrantLock要差。(当然一般情况下Synchronized就足够满足大部分项目了,且使用方便,不用考虑手动解锁的问题)
补充:
ReentrantLock使用时尽量配合try/finally语句块来保证每次‘锁’都被手动‘解锁’,即lock()和unlock()调用方法次数要保持一致,不然可能会死锁。例如:
数量一致时:同一个锁对象在被多次调用时可以正常运行。
public class ReentryLock3 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { try { lock.lock(); System.out.println("threadName:" + Thread.currentThread().getName()); } finally { lock.unlock(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { try { lock.lock(); System.out.println("threadName:" + Thread.currentThread().getName()); } finally { lock.unlock(); } } } }).start(); } }
数量不一致时:同一个锁对象在被多次调用时无法保证正常运行。
public class ReentryLock3 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); new Thread(new Runnable() { @Override public void run() { //此处改为多次lock只在外面unlock一次用作测试 try { for (int i = 0; i < 5; i++) { lock.lock(); System.out.println("threadName:" + Thread.currentThread().getName()); } } finally { lock.unlock(); } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { try { lock.lock(); System.out.println("threadName:" + Thread.currentThread().getName()); } finally { lock.unlock(); } } } }).start(); } }
结果:因为第一个线程未完全解锁,所以第二个线程无法正常获取到锁,导致死锁程序无法运行下去。