与synchronized区别

  • 首先最大的不同:synchronized是基于JVM层面实现的,而Lock是基于JDK层面实现的
  • synchronized加上锁以后是不能中断的,不能通过一些其他的java语法来中断他;也就是synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断
  • ReentrantLock可以设置超时时间,规定时间内获取不到线程就放弃;而synchronized是必须等待。
  • ReentrantLock可以设置公平锁,也就是先到先得,而不是像synchronized一样在阻塞队列中随机选取一个,让线程之间随机去争抢,如果线程数多,那么可能有的线程永远得不到锁,所以synchronized是不公平锁。
  • synchronized语法是通过关键字synchronized来实现加锁的,加锁和解锁的过程自动进行;ReentrantLock语法是通过ReentrantLock对象来实现加锁的,加锁和解锁的过程需要手动进行,lock获取锁,unlock释放锁。
  • ReentrantLock等待唤醒机制使用的是Condition接口的await/singnal/singnalAll,synchronized使用的是wait/notify/notifyAll,他们本质的区别在于ReentrantLock支持多个条件变量(多个休息室),而synchronized只有一个条件变量(一个休息室)
  • 相同点都支持可重入

常用方法

  • lock():获得锁,如果锁被占用则等待,不能被打断。采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生,如下:

    1. //手动加锁和解锁,他是JUC(java.util.concurrent)下的包
    2. public void test{
    3. ReentrantLock lock = new ReentrantLock();
    4. //获取锁
    5. lock.lock();
    6. try {
    7. //临界区
    8. } finally {
    9. //释放锁
    10. lock.unlock();
    11. }
    12. }
  • lockInterruptibly()/,intə’rʌptəbəl/ :获得锁,如果没其他线程竞争那么此方法就会获取lock对象锁;如果有竞争就进入阻塞队列,可以被其他线程用interrupt方法打断【对应着ReentrantLock特性可中断】。

  • tryLock():尝试获得锁,如果成功,则返回true;失败返回false。此方法不等待,立即返回【对应着ReentrantLock特性超时时间】。
  • unlock():释放锁。

    1、可重入

    可重入是指同一个线程如果首次获得了这把锁,那么因为他是这把锁的拥有者,因此有权利再次获取这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。

    2、可中断

    通过lockInterruptibly方法可以设置获取锁,然后通过interrupt方法进行打断,避免了死锁;但是lock方法不能被打断,就算执行了interrupt他还是会进行阻塞。
    lockInterruptibly方法能够中断等待获取锁的线程。当两个线程同时通过lock.lockInterruptibly获取某个锁时,假若此时线程A获取到了锁,而线程B只有等待,那么对线程B调用threadB.interrupt方法能够中断线程B的等待过程。 ```java 加锁时线程t1调用reentrantLock.lockInterruptibly()方法表示自己申请的是可打断锁,如果其他线程拥有了这把锁,为了防止线程1无限等待下去,可以在其他线程中调用t1.interrupt()打断t1线程的等待状态,让线程t1抛出InterruptedException异常,退出等待状态

public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { //如果没有竞争那么此方法就会获取lock对象锁 //如果有竞争就进入阻塞队列,可以被其他线程使用 interrupt 方法打断 System.out.println(“—-尝试获取锁—-“); try { lock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println(“没有获得锁,返回”); return; } try{ System.out.println(“—获取到锁—“); }finally { //释放锁 lock.unlock(); } }, “t1”);

  1. //主线程也加锁,上面代码就会进入阻塞,可以使用下面的interrupt进行打断,中断t1线程的等待
  2. lock.lock();
  3. t1.start();
  4. System.out.println("--打断锁--");
  5. t1.interrupt();

} ———————输出 —打断锁— —-尝试获取锁—- java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220) 没有获得锁,返回

  1. <a name="zlAtd"></a>
  2. ### 3、锁超时
  3. 通过**tryLock**实现,该方法的意思就是尝试获取到锁,为了防止无限制的等待,也避免了**死锁,**他有返回值是布尔类型的:成功获取则返回**true**;获取失败则返回**.**<br />**立即失败**<br />某线程调用**lock.tryLock()**尝试获取锁,如果没有获取成功,则放弃获取,如果获取了那么就往下执行。<br />**超时失败**<br />某线程调用**lock.tryLock(1, TimeUnit.SECONDS)**,在1s的时间内如果能够获取到锁就往下执行,如果没有就放弃获取。
  4. ```java
  5. public static void main(String[] args) {
  6. ReentrantLock lock = new ReentrantLock();
  7. Thread t1 = new Thread(() -> {
  8. System.out.println("---t1启动---");
  9. boolean tryLock = lock.tryLock();
  10. //2秒钟内获取到锁
  11. //boolean tryLock = lock.tryLock(2,TimeUnit.SECONDS);
  12. if(!tryLock){
  13. System.out.println("t1获取锁失败,返回");
  14. return;
  15. }
  16. try{
  17. System.out.println("--t1获得了锁--");
  18. }finally {
  19. lock.unlock();
  20. }
  21. }, "t1");
  22. lock.lock();
  23. System.out.println("--主线程获得了锁--");
  24. t1.start();
  25. }

4、公平锁

ReentrantLock 默认是不公平的,意思就是当一个线程释放锁之后,处于阻塞状态的线程并不是按照获取锁的先后顺序来获得锁的。
可以通过更改他的构造方法改为公平锁,如下改为true即为公平锁。
公平锁一般没有必要,会降低并发度,其实使用锁超时这种更好,适当的放弃锁。

  1. ReentrantLock lock = new ReentrantLock(true);

5、条件变量(等待、通知机制)

synchronized:wait()和notify/notifyAll()
ReentrantLock :awit()和singnal/signalAll

synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,
ReentrantLock类也可以实现,通过Condition接口与newCondition() 方法,然后在结合awit()和singnal/signalAll方法相结合可以实现等待/通知机制。
Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。
在使用notify/notifyAll()方法进行通知时,被通知的线程是有JVM选择的,使用ReentrantLock类结合Condition实例可以实现“选择性通知”,这也是他和synchronized的本质区别,而且是Condition接口默认提供的。
而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会唤醒所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。

synchronized 中也有一个条件变量,Monitor中的waitSet(休息室),当条件不满足时进入 waitSet 等待。
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的

  • synchronized中调用wait()方法的线程都在一个waitSet等消息
  • ReentrantLock 支持多个条件变量,调用await()则在调用该方法的条件变量处等待,唤醒也是根据不同的条件变量来唤醒对应的线程。

使用要点:

  • await 前需要获得锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行 ```java static ReentrantLock lock = new ReentrantLock(); static Condition waitCigaretteQueue = lock.newCondition(); static Condition waitbreakfastQueue = lock.newCondition(); static volatile boolean hasCigrette = false; static volatile boolean hasBreakfast = false;

public static void main(String[] args) { new Thread(() -> { try { lock.lock(); while (!hasCigrette) { try { // 没有烟,等待 waitCigaretteQueue.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug(“等到了它的烟”); } finally { lock.unlock(); } }).start(); new Thread(() -> { try { lock.lock(); while (!hasBreakfast) { try { waitbreakfastQueue.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug(“等到了它的早餐”); } finally { lock.unlock(); } }).start(); sleep(1); sendBreakfast(); sleep(1); sendCigarette(); }

private static void sendCigarette() { lock.lock(); try { log.debug(“送烟来了”); hasCigrette = true; // 唤醒 waitCigaretteQueue.signal(); } finally { lock.unlock(); } }

private static void sendBreakfast() { lock.lock(); try { log.debug(“送早餐来了”); hasBreakfast = true; //唤醒某个条件 waitbreakfastQueue.signal(); //唤醒全部 //waitbreakfastQueue.signalAll(); } finally { lock.unlock();} } ```