重入锁可以完全代替synchronized关键字.在JDK5.0的早期版本中,重入锁的性能远远好于synchronized,但是从JDK6.0开始.JDK在synchronized上做了大量的优化.使得两者的性能差距不大,
public class ReenterLock implements Runnable { public static ReentrantLock lock = new ReentrantLock(); public static int i = 0; /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see Thread#run() */ @Override public void run() { for (int j = 0; j < 10000000; j++) { lock.lock();//加锁 try { i++; } finally { lock.unlock();//释放锁 } } } public static void main(String[] args) throws InterruptedException { ReenterLock t1 = new ReenterLock();//线程实例 Thread th1 = new Thread(t1); Thread th2 = new Thread(t1); th1.start(); th2.start(); th1.join(); th2.join(); System.out.println("i = " + i); } }
这里 我们使用重入锁保护临界区资源i 确保多线程对i操作的安全性,我们可以看出,与synchronized相比,重入锁有着显示的操作过程,开发人员必须手动指定何时加锁,何时释放锁,也正因为这样,重入锁对于逻辑控制的灵活性要远远好于synchronized,但是要必须释放锁,否则 其他线程就没有机会现在方法临界区资源了!另外重入锁允许一个线程连续几次获取同一把锁,但是释放锁的时候也要释放相同次数.
- 中断响应 与synchronized相比,如果一个线程在等待锁,那么结果只要两种情况,1 获得这把锁执行, 2 他保持等待状态,而使用重入锁,则提供了另外一种可能性,那就是线程可以被中断,也就是在等待过程中,程序可以根据需要取消对锁的请求.
下面代码产生了一个死锁,但是得益于锁的中断,我们可以很轻松的解决死锁.
public class IntLock implements Runnable { public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; public IntLock(int lock) { this.lock = lock; } /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see Thread#run() */ @Override public void run() { try { if (lock == 1) { lock1.lockInterruptibly();//锁1 这是一个可以对中断进行想要的锁申请动作! try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } lock2.lockInterruptibly();//锁2 加锁 } else { lock2.lockInterruptibly(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } lock1.lockInterruptibly();//锁1 加锁 } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock1.isHeldByCurrentThread()) { //判断持有自己锁的线程是否是当前线程 lock1.unlock(); } if (lock2.isHeldByCurrentThread()) { lock2.unlock(); } System.out.println(Thread.currentThread().getId() + ":线程退出"); } } public static void main(String[] args) throws InterruptedException { IntLock r1 = new IntLock(1);//线程实例 1 IntLock r2 = new IntLock(2);//线程实例 2 Thread t1 = new Thread(r1);//线程1 Thread t2 = new Thread(r2);//线程2 t1.start(); t2.start(); Thread.sleep(1000);//Main线程 休眠1s t2.interrupt();//中断其中一个线程 } }
线程t1和t2启动后,t1占用rock1 在占用rock2 t2先占用rock2 然后请求rock1 因此很容易形成互相等待, 当我们让t2中断时,他放弃了申请lock1 然后释放了lock2 实际上是 t1线程 完成任务正常退出,而t2 是中断的,
- 锁申请等待限时 除了等待通知之外,要避免死锁还有另外一种方法,那就是限时等待, 就是规定一个时间,超出时间没有拿到锁 就退出
public class TimeLock implements Runnable { public static ReentrantLock lock = new ReentrantLock(); /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see Thread#run() */ @Override public void run() { try { if (lock.tryLock(5, TimeUnit.SECONDS)) {//试图获取锁,等待5秒 如果超时那就false Thread.sleep(6000); } else { System.out.println("get lock failed"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } public static void main(String[] args) { TimeLock r1 = new TimeLock(); Thread t1 = new Thread(r1); Thread t2 = new Thread(r1); t1.start(); t2.start(); } }
在本例中,由于占用锁的线程会持有锁长达6s,故另一个线程无法在5s的等待时间内获得锁,因此,请求锁会失败!
ReentrantLock.tryLock()方法可以不带参数直接运行,在这种情况下,当前线程会尝试获得锁,如果锁并未被其他线程占用,则申请会成功!并立即返回true,如果锁被其他线程占用,则档期你先吃不会进行任何等待,而是立即返回false.
public class TryLock implements Runnable { public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; public TryLock(int lock) { this.lock = lock; } /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see Thread#run() */ @Override public void run() { if (lock == 1) { while (true) { if (lock1.tryLock()) { try { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if (lock2.tryLock()) { try { System.out.println(Thread.currentThread().getId() + ":My Job done"); return; } finally { lock2.unlock(); } } } finally { lock1.unlock(); } } } } else { while (true) { if (lock2.tryLock()) { try { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if (lock1.tryLock()) { try { System.out.println(Thread.currentThread().getId() + ":My Job done"); return; } finally { lock1.unlock(); } } } finally { lock2.unlock(); } } } } } public static void main(String[] args) { TryLock r1 = new TryLock(1); TryLock r2 = new TryLock(2); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); } }
上述代码中.采用了非常容易死锁的加锁顺序,在一般情况下,这会导致t1和t2互相等待. 但是使用tryLock()后,就会得到大大改善,线程不会傻傻的等待,而是不停的尝试.因此只要时间足够的长,线程总是会得到所有需要的资源.
- 公平锁 在大多情况下.锁的申请都是非公平的.两个线程同时申请锁a,谁先获得锁a呢 这是不一定的,系统只是会从这个锁的等待队列中随机挑选一个.而公平的锁,则不是这样的,他会按照时间的先后顺序,保证先到先得,后到后的,公平锁的一大特点就是,不会产生饥饿现象,只要你排队,最终还是可以得到资源的, 如果我们使用synchronized关键字进行锁控制,那么产生的锁就是非公平的,而重入锁循序我们对其公平性进行设置,下面是他的构造器
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
sync = fair ? new FairSync() : new NonfairSync();
}
当参数fair为true时,表示锁是公平的,公平锁必须要求系统维护一个有序的队列,因此实现成本比较高,性能相对低下,因此默认锁是非公平的.