Java并发基础--Lock的学习

一、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可以提高多个线程进行读操作的效率

posted @ 2018-07-18 23:17  ~直落银河九天~  阅读(257)  评论(0)    收藏  举报