一、Lock的出现
Lock的主要作用实现线程之间的同步互斥,与synchronized关键字的效果是一样的,synchronized是Java语言内置的特性,那么为什么又出现了Lock呢?原因是synchronized不是完美的,那么synchronized的缺陷在哪里呢?
①、通过synchronized实现同步,如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
②、通过synchronized实现同步的时候,无法知道线程是否获取到了锁。
为了弥补synchronized的缺陷,Java 5中引入了新的锁机制——java.util.concurrent.locks中的显式的互斥锁。Lock接口,提供了比synchronized更加广泛的锁操作。它的主要实现类:ReentrantLock、ReetrantReadWriteLock.ReadLock和ReetrantRead.WriteLock.即重入锁、读锁和写锁。Lock必须被显式的创建和释放,为了保证最终锁一定被释放,经常将虎互斥区置放在try语句中,并在finally中释放锁,特别是当有return语句的时候,return语句必须放在try语句中,以确保unlock不会过早的发生,从而将数据暴露给下面的任务。使用比较多的是ReentrantLock。示例如下:
1 public class LockDemo { 2 private Lock lock = new ReentrantLock(); 3 4 public void testMethod(){ 5 try { 6 lock.lock(); 7 for(int i=0;i<3;i++){ 8 System.out.println(Thread.currentThread().getName()+" print...."); 9 } 10 } catch (Exception e) { 11 e.printStackTrace(); 12 }finally{ 13 lock.unlock(); 14 } 15 } 16 17 public static void main(String[] args) { 18 LockDemo lockDemo = new LockDemo(); 19 20 new Thread(new MyThread1(lockDemo)).start(); 21 new Thread(new MyThread1(lockDemo)).start(); 22 23 } 24 25 } 26 class MyThread1 implements Runnable{ 27 private LockDemo lockDemo; 28 public MyThread1(LockDemo lockDemo){ 29 this.lockDemo = lockDemo; 30 } 31 @Override 32 public void run() { 33 lockDemo.testMethod(); 34 } 35 36 }
结果输出:
Thread-0 print.... Thread-0 print.... Thread-0 print.... Thread-1 print.... Thread-1 print.... Thread-1 print....
总结:
- Lock是一个接口,它有不同的实现类,通过它的实现类可以实现多线程间的同步互斥。
- Lock与synchronized最大的不同在于,synchronized不需要用户手动释放锁,在线程完成了同步块或者同步方法后自动释放锁(或者出现异常也自动释放锁)。而Lock锁需要用户手动释放,如果忘记释锁,可能造成死锁等后果。
二、Lock的详细学习
通过源码可以知道,Lock是一个接口,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。newCondition()主要是用来创建Condition对象的,线程对象可以注册在指定的Condition上,Condition起到一个对象监视器的作用,通过Condition实例从而进行线程通知,在调用线程上更加灵活。
1.lock()方法
lock方法是使用最多的一个方法,即用来获取锁的,如果锁已经被其他线程占用,则进行等待。经常需要结合try语句一起使用。目的是为了将锁的释放工作放在finally中进行,保证锁的最终释放。如下:
Lock lock = new ReentrantLock(); lock.lock(); try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 }
2.tryLock()方法
tryLock()是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
3.tryLock(long time, TimeUnit unit)方法
tryLock(long time, TimeUnit unit)方法类似tryLock(),区别在于这个方法在拿不锁的时候,不会立即返回,而是等待指定的时间,如果超过指定时间还未获取到锁,则立即返回false,获取锁失败。
Lock lock = new ReentrantLock(); if(lock.tryLock(3, TimeUnit.SECONDS)) { try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 } }else { //如果不能获取锁,则直接做其他事情 }
4.lockInterruptibly()方法
lockInterruptibly()方法,比较特使,可以说是Lock锁的特点之一,即线程在获取锁的过程中,如果一直获取不到锁,它不会立即放弃,直到这个线程被中断。即它能够响应中断, void lockInterruptibly() throws InterruptedException;接口的方法定义是声明了异常的,lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。它的使用形式如下:
public void method() throws InterruptedException { lock.lockInterruptibly(); try { //..... } finally { lock.unlock(); } }
5.newCondition()方法
在使用notify()/notifyAll()方法的时候,被唤醒的线程是JVM随机选择的,最终能够获取锁的线程也许不是我们想要的。在Lock中结合Condition类可以实现“选择性通知”。即可以通过Condition对象唤醒指定的线程。示例如下:
1 public class LockConditionDemo { 2 3 private Lock lock = new ReentrantLock(); 4 private Condition condition = lock.newCondition(); 5 6 public void testMethod(){ 7 try { 8 lock.lock(); 9 System.out.println(Thread.currentThread().getName()+" test begin"); 10 condition.await();//当前线程进入等待状态,释放lock锁 11 System.out.println(Thread.currentThread().getName()+" test end"); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 }finally{ 15 lock.unlock(); 16 System.out.println(Thread.currentThread().getName()+" 释放锁"); 17 } 18 } 19 20 public void testMethod2(){ 21 try { 22 lock.lock(); 23 System.out.println(Thread.currentThread().getName()+" test2 begin"); 24 condition.signal();//通知处于等待lock锁状态的线程 25 System.out.println(Thread.currentThread().getName()+" test2 end"); 26 }finally{ 27 lock.unlock(); 28 System.out.println(Thread.currentThread().getName()+" 释放锁"); 29 } 30 } 31 32 public static void main(String[] args) { 33 LockConditionDemo lockConditionDemo = new LockConditionDemo(); 34 new Thread(new Mythread3(lockConditionDemo)).start(); 35 new Thread(new Mythread4(lockConditionDemo)).start(); 36 } 37 38 } 39 40 class Mythread3 implements Runnable{ 41 private LockConditionDemo lockConditionDemo; 42 43 Mythread3(LockConditionDemo lockConditionDemo){ 44 this.lockConditionDemo = lockConditionDemo; 45 } 46 47 @Override 48 public void run() { 49 lockConditionDemo.testMethod(); 50 } 51 } 52 53 class Mythread4 implements Runnable{ 54 private LockConditionDemo lockConditionDemo; 55 56 Mythread4(LockConditionDemo lockConditionDemo){ 57 this.lockConditionDemo = lockConditionDemo; 58 } 59 60 @Override 61 public void run() { 62 lockConditionDemo.testMethod2(); 63 } 64 }
结果输出:
Thread-0 test begin Thread-1 test2 begin Thread-1 test2 end Thread-1 释放锁 Thread-0 test end Thread-0 释放锁
三、Lock和synchronized的选择
1.lock和synchronized的不同
-
Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现。
-
synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。
-
Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
-
通过Lock可以知道有没有成功获取锁,而synchronized却无法办到
-
Lock可以提高多个线程进行读操作的效率