Java多线程(三)之 Lock 锁

一、介绍

Jdk1.5以后,在java.util.concurrent.locks包下,有一组实现线程同步的接口和类,说到线程的同步,可能大家都会想到synchronized关键字,

这是java内置的关键字,用来处理线程同步的,但这个关键字有很多的缺陷,使用起来也不是很方便和直观,所以就出现了Lock,下面,我们

就来对比着讲解Lock。

通常我们在使用synchronized关键字的时候会遇到下面这些问题:

  1. 不可控性,无法做到随心的加锁和释放锁。
  2. 效率比较低下,比如我们现在并发的读两个文件,读与读之间是互不影响的,但如果给这个读的对象使用synchronized来实现同步的话,那么只要有一个线程进入了,那么其他的线程都要等待。
  3. 无法知道线程是否获取到了锁。

而上面synchronized的这些问题,Lock都可以很好的解决,并且jdk1.5以后,还提供了各种锁,例如读写锁,但有一点需要注意,使用 synchronized 关键时,无须手动释放锁,但使用 Lock 必须手动释放锁。下面我们就来学习一下Lock锁。

Lock是一个上层的接口,其原型如下,总共提供了6个方法:

public interface Lock {
  // 用来获取锁,如果锁已经被其他线程获取,则一直等待,直到获取到锁
   void lock();
  // 该方法获取锁时,可以响应中断,比如现在有两个线程,一个已经获取到了锁,另一个线程调用这个方法正在等待锁,但是此刻又不想让这个线程一直在这死等,可以通过
    调用线程的Thread.interrupted()方法,来中断线程的等待过程
  void lockInterruptibly() throws InterruptedException;
  // tryLock方法会返回bool值,该方法会尝试着获取锁,如果获取到锁,就返回true,如果没有获取到锁,就返回false,但是该方法会立刻返回,而不会一直等待
   boolean tryLock();
  // 这个方法和上面的tryLock差不多是一样的,只是会尝试指定的时间,如果在指定的时间内拿到了锁,则会返回true,如果在指定的时间内没有拿到锁,则会返回false
   boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  // 释放锁
   void unlock();
  // 实现线程通信,相当于wait和notify,后面会单独讲解
   Condition newCondition();
}

二、ReentrantLock

java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

reentrant 锁意味着什么呢?

简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。
 

三、lock,unlock的使用

lock.lock(); lock.unlock(); 包起来的代码,就和  synchronized 是一样的效果。 
同一个lock对象锁是同一把;代码中serviceA ,serviceB 中的lock是同一把锁哦,谁先获取谁就有控制权。

package cn.thread.lock.reentrant;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService {
    //使用ReentrantLock
    private Lock lock = new ReentrantLock();

    public void serviceA() {
        lock.lock();//serviceA ,serviceB 中的lock是同一把锁哦,谁先获取谁就有控制权。
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "----Ai=" + i);
        }
        lock.unlock();
    }

    public void serviceB() {
        lock.lock();
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "----Bi=" + i);
        }
        lock.unlock();
    }
}

public class ReentrantDome1 {

    public static void main(String orgs[]) {
        final MyService start = new MyService();
        new Thread(new Runnable() {
            public void run() {
                start.serviceA();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceB();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceA();
            }
        }).start();
    }
}

三、Condition接口实现线程通信 await()、signal()、signalAll

ConditionObject 监视器方法(waitnotifynotifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。

作为一个示例,假定有一个绑定的缓冲区,它支持 puttake 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

lock 通信使用的方法 await,signal,signalAll

  • await 等价于 wait
  • signal 等价于 notify
  • signalAll 等价于 notifyAll

A线程先运行,获取到了锁,但是进入await之后,交出锁权限,线程挂起;B线程获取到锁,B线程唤醒A线程,B线程运行完毕,A线程接着运行。

package cn.thread.lock.reentrant;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService2 {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void serviceA() {
        try {
            lock.lock();
            condition.await();//线程挂起,进入等待中,和wait一样。
            System.out.println(Thread.currentThread().getName() + "----Ai=");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void serviceB() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "----Bi=");
            condition.signal();//唤醒被await的线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

public class ReentrantDome2 {

    public static void main(String orgs[]) {
        final MyService2 start = new MyService2();
        new Thread(new Runnable() {
            public void run() {
                start.serviceA();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceB();
            }
        }).start();
    }
}
发布了69 篇原创文章 · 获赞 43 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/fox_bert/article/details/99948203