Java多线程之Lock详解

Java多线程之Lock详解

 

Lock的工作原理

 

获得锁

  • 从线程中读取表示锁状态的变量
  • 如果状态为0,就改为1,如果有多个线程,只会有一个成功
  • 如果修改成功就获得了锁,进入维护队列
  • 如果失败,则进入等待队列并阻塞自身,此时线程一直被阻塞在lock方法中,没有从该方法中返回
  • 如果表示状态的变量的值为1,那么将当前线程放入等待队列中,然后将自身阻塞(被唤醒后仍然在lock方法中,并从下一条语句继续执行,这里又会回到第1步重新开始)

注意: 唤醒并不表示线程能立刻运行,而是表示线程处于就绪状态,仅仅是可以运行而已

 

释放锁

  • 释放锁的线程将状态变量的值从1设置为0,并唤醒等待(锁)队列中的队首节点,释放锁的线程从就从unlock方法中返回,继续执行线程后面的代码
  • 被唤醒的线程(队列中的队首节点)和可能和未进入队列并且准备获取的线程竞争获取锁,重复获取锁的过程

注意:可能有多个线程同时竞争去获取锁,但是一次只能有一个线程去释放锁,队列中的节点都需要它的前一个节点将其唤醒,例如有队列A<-B-<C ,即由A释放锁时唤醒B,B释放锁时唤醒C

 

 

 

主要方法和使用

 

实际上Lock是一个接口,常用的方法有:

 

//尝试获取锁,获取成功则返回,否则阻塞当前线程
void lock(); 

//尝试获取锁,线程在成功获取锁之前被中断,则放弃获取锁,抛出异常 
void lockInterruptibly() throws InterruptedException; 

//尝试获取锁,获取锁成功则返回true,否则返回false 
boolean tryLock(); 

//尝试获取锁,若在规定时间内获取到锁,则返回true,否则返回false,未获取锁之前被中断,则抛出异常 
boolean tryLock(long time, TimeUnit unit) 
                                   throws InterruptedException; 

//释放锁
void unlock(); 

//返回当前锁的条件变量,通过条件变量可以实现类似notify和wait的功能,一个锁可以有多个条件变量
Condition newCondition();

 

 

使用方法

 

多线程下访问(互斥)共享资源时, 访问前加锁,访问结束以后解锁,解锁的操作推荐放入finally块中。

 

Lock l = ...; //根据不同的实现Lock接口类的构造函数得到一个锁对象 
l.lock(); //获取锁位于try块的外面 
try { 
      // access the resource protected by this lock 
} finally { 
     l.unlock(); 
}

 

注意,加锁位于对资源访问的try块的外部,特别是使用lockInterruptibly方法加锁时就必须要这样做,这为了防止线程在获取锁时被中断,这时就不必(也不能)释放锁。

 

try {
     l.lockInterruptibly();//获取锁失败时不会执行finally块中的unlock语句
      try{
          // access the resource protected by this lock
     }finally{
          l.unlock();
     }
} catch (InterruptedException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
}

 

 

 

 

 

Lock的实现

 

Lock有三个实现类,一个是ReentrantLock,另两个是ReentrantReadWriteLock类中的两个静态内部类ReadLockWriteLock

 

  • ReentrantLock(可重入锁):互斥锁,次只能一个线程拥有互斥锁,其他线程只有等待
  • ReentrantReadWriteLock(读写锁):将读和写分离开两个锁,读锁不互斥,写锁互斥
  • 自旋锁:一次只能有一个进程进入临界区,读写锁是自旋锁的一个特例。

 

 

ReentrantLock特性

 

  • 轮询锁:在每次请求资源的时候,就用tryLock(),如果资源被占用,就返回,而不是一直阻塞。
  • 定时锁:在进行轮询的时候,还可以设置时间,超时后就返回,而不是一直阻塞。
  • 公平性:所谓公平锁,线程将按照他们发出请求的顺序来获取锁,不允许插队;但在非公平锁上,则允许插队。
  • 可中断锁:lockInterruptibly方法能够在获取锁的同时保持对中断的响应,因此无需创建其它类型的不可中断阻塞操作。

ReentrantLock最大的作用

 

  • 避免死锁
  • 使用灵活,自己加锁和解锁

 

 

读写锁特性

 

  • 拥有可重入锁特性的情况下,还有下面特性
  • 多个读者可以同时进行读
  • 写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
  • 写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)

读写锁最大的作用

 

  • 提高读写速度,当很多读线程,很少写线程时,可以选择使用

 

 

应用场景

 

1. 以队列操作为例:

线程A对队列负责将数据写入队列。须采取“互斥锁”或“读写锁的写锁”

线程B队列负责从队列读出数据。须采取“互斥锁”或“读写锁的写锁”,读队列操作,不可采取“读写锁的读锁”,因为从队列读出数据时,需要更改队列本身的下标索引,如果多个线程同时操作该队列的话,就会导致队列下标索引混乱。

但是对队列的查询操作则最好采取“读写锁的读锁”来提高效率。

Condition接口

一个Condition实例的内部实际上维护了一个队列,队列中的节点表示由于(某些条件不满足而)线程自身调用await方法阻塞的线程。Condition接口中有两个重要的方法,即 await方法和 signal方法。

线程调用这个方法之前该线程必须已经获取了Condition实例所依附的锁。

这样的原因有两个

  • 对于await方法,它内部会执行释放锁的操作,所以使用前必须获取锁。
  • 对于signal方法,是为了避免多个线程同时调用同一个Condition实例的singal方法时引起的(队列)出列竞争。

Condition方法的执行流程

await方法:

      1. 入列到条件队列(注意这里不是等待锁的队列)

      2. 释放锁

      3. 阻塞自身线程

      ------------被唤醒后执行-------------

      4. 尝试去获取锁(执行到这里时线程已不在条件队列中,而是位于等待(锁的)队列中,参见signal方法)

            4.1 成功,从await方法中返回,执行线程后面的代码

            4.2 失败,阻塞自己(等待前一个节点释放锁时将它唤醒)

注意:await方法时自身线程调用的,线程在await方法中阻塞,并没有从await方法中返回,当唤醒后继续执行await方法中后面的代码(也就是获取锁的代码)。可以看出await方法释放了锁,又尝试获得锁。当获取锁不成功的时候当前线程仍然会阻塞到await方法中,等待前一个节点释放锁后再将其唤醒。

signal方法:

      1. 将条件队列的队首节点取出,放入等待锁队列的队尾

      2. 唤醒该节点对应的线程

注意:signal是由其它线程调用



 

Condition主要方法

// 让线程进入等通知待状态 
void await() throws InterruptedException; 
void awaitUninterruptibly();
 
//让线程进入等待通知状态,超时结束等待状态,并抛出异常  
long awaitNanos(long nanosTimeout) throws InterruptedException; 
boolean await(long time, TimeUnit unit) throws InterruptedException; 
boolean awaitUntil(Date deadline) throws InterruptedException; 

//将条件队列中的一个线程,从等待通知状态转换为等待锁状态 
void signal(); 

//将条件队列中的所有线程,从等待通知阻塞状态转换为等待锁阻塞状态
void signalAll();

Lock和Condition一起使用

下面这个例子,就是利用lock和condition实现B线程先打印一句信息后,然后A线程打印两句信息(不能中断),交替十次后结束

public class ConditionDemo {
    volatile int key = 0;
    Lock l = new ReentrantLock();
    Condition c = l.newCondition();
    
    public static  void main(String[] args){
        ConditionDemo demo = new ConditionDemo();
        new Thread(demo.new A()).start();
        new Thread(demo.new B()).start();
    }
    
    class A implements Runnable{
        @Override
        public void run() {
            int i = 10;
            while(i > 0){
                l.lock();
                try{
                    if(key == 1){
                        System.out.println("A is Running");
                        System.out.println("A is Running");
                        i--;
                        key = 0;
                        c.signal();
                    }else{
                     c.awaitUninterruptibly();                        
                    }
                    
                }
                finally{
                    l.unlock();
                }
            }
        }
        
    }
    
    class B implements Runnable{
        @Override
        public void run() {
            int i = 10;
            while(i > 0){
                l.lock();
                try{
                    if(key == 0){
                        System.out.println("B is Running");
                        i--;
                        key = 1;
                        c.signal();
                    }else{
                     c.awaitUninterruptibly();                        
                    }
                    
                }
                finally{
                    l.unlock();
                }
            }
        }    
    }
}

 

猜你喜欢

转载自youyu4.iteye.com/blog/2351370