Java多线程总结(8)concurrent.locks包下的锁机制的使用

来源:互联网 发布:淘宝怎样把东西卖出去 编辑:程序博客网 时间:2024/05/21 05:19

1 Lock与ReadWriteLock

1.1 Lock

public interface Lock {    void lock();    void lockInterruptibly() throws InterruptedException;    boolean tryLock();    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;    void unlock();    Condition newCondition();}

  在Lock中声明了四个方法来获取锁,那么这四个方法有何区别呢?
  lock()
  首先lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
  由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的形式:

Lock lock = ...;lock.lock();try{    //处理任务}catch(Exception ex){}finally{    lock.unlock();   //释放锁}

  tryLock()和tryLock(long time, TimeUnit unit)
  tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
  tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
  通过tryLock来获取锁的基本形式:

Lock lock = ...;if(lock.tryLock()) {    try{        //处理任务    } catch(Exception ex){    } finally {        lock.unlock();   //释放锁    } }else {    //如果不能获取锁,则直接做其他事情}

  lockInterruptibly()
  lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
  由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。

  lockInterruptibly()一般的使用形式:

public void method() throws InterruptedException {    lock.lockInterruptibly();    try {       //.....    }    finally {        lock.unlock();    }  }

  注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。
  因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,在进行等待的情况下,是可以响应中断的。
  而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

1.2 Lock实现类ReentrantLock

  ReentrantLock,意思是“可重入锁”,关于可重入锁的概念在下一节讲述。下面通过一些实例具体看一下如何使用ReentrantLock。
  tryLock()

public class TrylockMethodTest {    public static void main(String[] args) {        SharedDataService service = new SharedDataService();        new Thread(new Runnable() {            @Override            public void run() {                service.put("msg");            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                service.put("Message");            }        }).start();    }    static class SharedDataService {        private List<String> data = new ArrayList<String>();        // 创建一个非公平的可重入锁        private Lock lock = new ReentrantLock();        public void put(String msg) {            /*             * Acquires the lock only if it is free at the time of invocation.             * 返回true说明获取锁成功             */            if(lock.tryLock()) {                try {                    System.out.println(Thread.currentThread().getName()+"得到了锁");                    data.add(msg);                    Thread.sleep(1000);                    System.out.println(Thread.currentThread().getName()+"存入数据"+msg);                } catch (Exception e) {                    // TODO: handle exception                } finally {                    lock.unlock();                    System.out.println(Thread.currentThread().getName()+"释放了锁");                }            } else {                System.out.println(Thread.currentThread().getName()+"获取锁失败!");            }        }    }}

  运行结果:

Thread-0得到了锁Thread-1获取锁失败!Thread-0存入数据msgThread-0释放了锁

  lockInterruptibly()

public class LockInterruptiblyMethodTest {    public static void main(String[] args) throws InterruptedException {        SharedDataService service = new SharedDataService();        Thread thread0 = new Thread(new Runnable() {            @Override            public void run() {                try {                    service.put("msg");                } catch (InterruptedException e) {                    e.printStackTrace();                    System.out.println(Thread.currentThread().getName()+"在等待获取锁时被中断!");                }            }        });        thread0.start();        Thread thread1 = new Thread(new Runnable() {            @Override            public void run() {                try {                    service.put("Message");                } catch (InterruptedException e) {                    e.printStackTrace();                    System.out.println(Thread.currentThread().getName()+"在等待获取锁时被中断!");                }            }        });        thread1.start();        // 主线程中中断thread1等待获取锁的阻塞状态        Thread.sleep(1000);        thread1.interrupt();    }    static class SharedDataService {        private List<String> data = new ArrayList<String>();        // 创建一个非公平的可重入锁        private Lock lock = new ReentrantLock();        public void put(String msg) throws InterruptedException {            lock.lockInterruptibly();            try {                System.out.println(Thread.currentThread().getName()+"得到了锁");                data.add(msg);                Thread.sleep(4000);                System.out.println(Thread.currentThread().getName()+"存入数据"+msg);            } catch (Exception e) {                // TODO: handle exception            } finally {                lock.unlock();                System.out.println(Thread.currentThread().getName()+"释放了锁");            }        }    }}

  运行结果:
  这里写图片描述

1.3 ReadWriteLock

public interface ReadWriteLock {    /**     * 返回用于读操作的锁     */    Lock readLock();    /**     * 返回用于写操作的锁     */    Lock writeLock();}

  一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。下面的ReentrantReadWriteLock实现了ReadWriteLock接口。

1.4 ReadWriteLock的实现类ReentrantReadWriteLock

  下面通过几个例子来看一下ReentrantReadWriteLock具体用法:(主要看readLock和writeLock)
  假如有多个线程要同时进行读操作的话,先看一下synchronized达到的效果:(代码比较简单,省略了)
        这里写图片描述
  而采用ReadWriteLock的读操作锁如下:

public class ReadLockTest {    public static void main(String[] args) {        SharedDataService service = new SharedDataService();        new Thread(new Runnable() {            @Override            public void run() {                service.get();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                service.get();            }        }).start();    }    static class SharedDataService {        // 创建一个读写锁        private ReadWriteLock rwl = new ReentrantReadWriteLock();        public void get() {            // 获取读操作的锁ReadLock            rwl.readLock().lock();            try {                System.out.println(Thread.currentThread().getName()+"得到了锁");                for (int i = 0; i < 6; i++) {                    Thread.sleep(500);                    System.out.println(Thread.currentThread().getName()+"读取数据");                }            } catch (Exception e) {                // TODO: handle exception            } finally {                rwl.readLock().unlock();// 释放ReadLock                System.out.println(Thread.currentThread().getName()+"读操作完毕释放了锁");            }        }    }}

        这里写图片描述
  说明采用读写锁,thread0和thread1在同时进行读操作。这样就大大提升了读操作的效率。
  不过要注意的是:

  1. 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
  2. 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
  在性能上来说,如果竞争资源不激烈,Lock和synchronized的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

1.5 利用读写锁实现简单的缓存系统

关于缓存系统:
  缓存系统一般位于用户和数据库中间的一个环节,用户直接访问数据库的时间是远大于直接访问内存,所以有了缓存区后用户访问数据时,先访问缓存区,当缓存区有用户需要的数据时直接拿走,当缓存区没有这样的数据,访问数据库并把访问所得的数据放在缓存区,这样当下一个需要这个数据的用户就直接访问内存即可得到。
  JDK文档中给出的Demo:

public class CashedDataDemo {    public static void main(String[] args) {        CachedDataService cacheService = new CachedDataService();        for (int i = 0; i < 10; i++) {            new Thread(new Runnable() {                public void run() {                    cacheService.processCachedData();                }            }).start();        }    }    static class CachedDataService {        String data;        volatile boolean cacheValid = false; // 缓存是否有效,即是否有数据        final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();        void processCachedData() {            rwl.readLock().lock(); // 上读锁            if (!cacheValid) {                /* 在获取写锁之前一定要先释放读锁 */                rwl.readLock().unlock();                /* 其中一个线程获得写锁 */                rwl.writeLock().lock();                try {                    /*                     * 注意此处需要再次检查缓存的状态,因为其他的线程可能阻塞在获取写锁的地方,                     * 当实际写缓存的线程写完数据释放写锁时,其他的线程仍然获取到写锁,再此写数据了。                     */                    if (!cacheValid) {                        data = "get new data!";                        System.out.println(Thread.currentThread().getName() + "写数据到缓存:" + data);                        Thread.sleep(2000);                        cacheValid = true; // 设置缓存状态                    }                } catch(InterruptedException e) {                }finally {                    rwl.writeLock().unlock();                }                rwl.readLock().lock();            }            try {                System.out.println(Thread.currentThread().getName() + "从缓存中获取到数据:" + data);            } finally {                rwl.readLock().unlock();            }        }    }}

  输出:
      这里写图片描述

ReentrantReadWriteLocks can be used to improve concurrency in some uses of some kinds of Collections. This is typically worthwhile only when the collections are expected to be large, accessed by more reader threads than writer threads, and entail operations with overhead that outweighs synchronization overhead. For example, here is a class using a TreeMap that is expected to be large and concurrently accessed.

 class RWDictionary {   private final Map m = new TreeMap();   private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();   private final Lock r = rwl.readLock();   private final Lock w = rwl.writeLock();   public Data get(String key) {     r.lock();     try { return m.get(key); }     finally { r.unlock(); }   }   public String[] allKeys() {     r.lock();     try { return m.keySet().toArray(); }     finally { r.unlock(); }   }   public Data put(String key, Data value) {     w.lock();     try { return m.put(key, value); }     finally { w.unlock(); }   }   public void clear() {     w.lock();     try { m.clear(); }     finally { w.unlock(); }   } }

2 Condition

  Condition可以替代传统的线程间通信,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。传统线程的通信方式,Condition都可以实现。

  注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。Condition的强大之处在于它可以为多个线程间建立不同的Condition。

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

——其实就是java.util.concurrent.ArrayBlockingQueue的功能

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;  //写索引   int takeptr; //读索引   int 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();//解除锁定      }    } }

  参考:
http://www.cnblogs.com/dolphin0520/p/3923167.html
http://my.oschina.net/91jason/blog/385500?fromerr=JhsMinq6
http://blog.csdn.net/it_man/article/details/8972001

1 0