zoukankan      html  css  js  c++  java
  • java并发之Lock

    从Java 5之后。在java.util.concurrent.locks包下提供了第二种方式来实现同步訪问。那就是Lock。

     1.Lock

      首先要说明的就是Lock,通过查看Lock的源代码可知,Lock是一个接口:

    1
    2
    3
    4
    5
    6
    7
    8
    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()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。

    newCondition()这种方法暂且不在此讲述,会在后面的线程协作一文中讲述。

      在Lock中声明了四个方法来获取锁,那么这四个方法有何差别呢?

      首先lock()方法是寻常使用得最多的一个方法,就是用来获取锁。假设锁已被其它线程获取,则进行等待。

      因为在前面讲到假设採用Lock,必须主动去释放锁。而且在发生异常时。不会自己主动释放锁。因此一般来说。使用Lock必须在try{}catch{}块中进行,而且将释放锁的操作放在finally块中进行。以保证锁一定被被释放。防止死锁的发生。

    通常使用Lock来进行同步的话,是以以下这样的形式去使用的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Lock lock = ...;
    lock.lock();
    try{
        //处理任务
    }catch(Exception ex){
         
    }finally{
        lock.unlock();   //释放锁
    }

      tryLock()方法是有返回值的,它表示用来尝试获取锁,假设获取成功,则返回true,假设获取失败(即锁已被其它线程获取)。则返回false,也就说这种方法不管怎样都会马上返回。在拿不到锁时不会一直在那等待。

      tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,仅仅只是差别在于这种方法在拿不到锁时会等待一定的时间。在时间期限之内假设还拿不到锁。就返回false。

    假设假设一開始拿到锁或者在等待期间内拿到了锁。则返回true。

      所以,普通情况下通过tryLock来获取锁时是这样使用的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Lock lock = ...;
    if(lock.tryLock()) {
         try{
             //处理任务
         }catch(Exception ex){
             
         }finally{
             lock.unlock();   //释放锁
         
    }else {
        //假设不能获取锁,则直接做其它事情
    }

       lockInterruptibly()方法比較特殊,当通过这种方法去获取锁时。假设线程正在等待获取锁,则这个线程可以响应中断,即中断线程的等待状态。

    也就使说,当两个线程同一时候通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁。而线程B仅仅有在等待。那么对线程B调用threadB.interrupt()方法可以中断线程B的等待过程。

      因为lockInterruptibly()的声明中抛出了异常。所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。

      因此lockInterruptibly()一般的使用形式例如以下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void method() throws InterruptedException {
        lock.lockInterruptibly();
        try {  
         //.....
        }
        finally {
            lock.unlock();
        }  
    }

      注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。由于本身在前面的文章中讲过单独调用interrupt()方法不能中断正在执行过程中的线程,仅仅能中断堵塞过程中的线程。

      因此当通过lockInterruptibly()方法获取某个锁时。假设不能获取到,仅仅有进行等待的情况下,是能够响应中断的。

      而用synchronized修饰的话。当一个线程处于等待某个锁的状态。是无法被中断的,仅仅有一直等待下去。

      2.ReentrantLock

      ReentrantLock,意思是“可重入锁”,关于可重入锁的概念在下一节讲述。

    ReentrantLock是唯一实现了Lock接口的类,而且ReentrantLock提供了很多其它的方法。以下通过一些实例看详细看一下怎样使用ReentrantLock。

      样例1。lock()的正确用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class Test {
        private ArrayList<Integer> arrayList = new ArrayList<Integer>();
        public static void main(String[] args)  {
            final Test test = new Test();
             
            new Thread(){
                public void run() {
                    test.insert(Thread.currentThread());
                };
            }.start();
             
            new Thread(){
                public void run() {
                    test.insert(Thread.currentThread());
                };
            }.start();
        }  
         
        public void insert(Thread thread) {
            Lock lock = new ReentrantLock();    //注意这个地方
            lock.lock();
            try {
                System.out.println(thread.getName()+"得到了锁");
                for(int i=0;i<5;i++) {
                    arrayList.add(i);
                }
            catch (Exception e) {
                // TODO: handle exception
            }finally {
                System.out.println(thread.getName()+"释放了锁");
                lock.unlock();
            }
        }
    }

       各位朋友先想一下这段代码的输出结果是什么?

    Thread-0得到了锁
    Thread-1得到了锁
    Thread-0释放了锁
    Thread-1释放了锁

      或许有朋友会问。怎么会输出这个结果?第二个线程怎么会在第一个线程释放锁之前得到了锁?原因在于。在insert方法中的lock变量是局部变量。每一个线程运行该方法时都会保存一个副本,那么理所当然每一个线程运行到lock.lock()处获取的是不同的锁。所以就不会发生冲突。

      知道了原因改起来就比較easy了,仅仅须要将lock声明为类的属性就可以。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class Test {
        private ArrayList<Integer> arrayList = new ArrayList<Integer>();
        private Lock lock = new ReentrantLock();    //注意这个地方
        public static void main(String[] args)  {
            final Test test = new Test();
             
            new Thread(){
                public void run() {
                    test.insert(Thread.currentThread());
                };
            }.start();
             
            new Thread(){
                public void run() {
                    test.insert(Thread.currentThread());
                };
            }.start();
        }  
         
        public void insert(Thread thread) {
            lock.lock();
            try {
                System.out.println(thread.getName()+"得到了锁");
                for(int i=0;i<5;i++) {
                    arrayList.add(i);
                }
            catch (Exception e) {
                // TODO: handle exception
            }finally {
                System.out.println(thread.getName()+"释放了锁");
                lock.unlock();
            }
        }
    }

       这样就是正确地使用Lock的方法了。

      样例2。tryLock()的用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class Test {
        private ArrayList<Integer> arrayList = new ArrayList<Integer>();
        private Lock lock = new ReentrantLock();    //注意这个地方
        public static void main(String[] args)  {
            final Test test = new Test();
             
            new Thread(){
                public void run() {
                    test.insert(Thread.currentThread());
                };
            }.start();
             
            new Thread(){
                public void run() {
                    test.insert(Thread.currentThread());
                };
            }.start();
        }  
         
        public void insert(Thread thread) {
            if(lock.tryLock()) {
                try {
                    System.out.println(thread.getName()+"得到了锁");
                    for(int i=0;i<5;i++) {
                        arrayList.add(i);
                    }
                catch (Exception e) {
                    // TODO: handle exception
                }finally {
                    System.out.println(thread.getName()+"释放了锁");
                    lock.unlock();
                }
            else {
                System.out.println(thread.getName()+"获取锁失败");
            }
        }
    }

       输出结果:

    Thread-0得到了锁
    Thread-1获取锁失败
    Thread-0释放了锁

      样例3,lockInterruptibly()响应中断的用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    public class Test {
        private Lock lock = new ReentrantLock();   
        public static void main(String[] args)  {
            Test test = new Test();
            MyThread thread1 = new MyThread(test);
            MyThread thread2 = new MyThread(test);
            thread1.start();
            thread2.start();
             
            try {
                Thread.sleep(2000);
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread2.interrupt();
        }  
         
        public void insert(Thread thread) throws InterruptedException{
            lock.lockInterruptibly();   //注意,假设须要正确中断等待锁的线程,必须将获取锁放在外面。然后将InterruptedException抛出
            try {  
                System.out.println(thread.getName()+"得到了锁");
                long startTime = System.currentTimeMillis();
                for(    ;     ;) {
                    if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                        break;
                    //插入数据
                }
            }
            finally {
                System.out.println(Thread.currentThread().getName()+"运行finally");
                lock.unlock();
                System.out.println(thread.getName()+"释放了锁");
            }  
        }
    }
     
    class MyThread extends Thread {
        private Test test = null;
        public MyThread(Test test) {
            this.test = test;
        }
        @Override
        public void run() {
             
            try {
                test.insert(Thread.currentThread());
            catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName()+"被中断");
            }
        }
    }

      执行之后。发现thread2可以被正确中断。

      3.ReadWriteLock

      ReadWriteLock也是一个接口。在它里面仅仅定义了两个方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public interface ReadWriteLock {
        /**
         * Returns the lock used for reading.
         *
         * @return the lock used for reading.
         */
        Lock readLock();
     
        /**
         * Returns the lock used for writing.
         *
         * @return the lock used for writing.
         */
        Lock writeLock();
    }

       一个用来获取读锁。一个用来获取写锁。

    也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程能够同一时候进行读操作。

    以下的ReentrantReadWriteLock实现了ReadWriteLock接口。

      4.ReentrantReadWriteLock

      ReentrantReadWriteLock里面提供了非常多丰富的方法,只是最基本的有两个方法:readLock()和writeLock()用来获取读锁和写锁。

      以下通过几个样例来看一下ReentrantReadWriteLock详细使用方法。

      假如有多个线程要同一时候进行读操作的话。先看一下synchronized达到的效果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class Test {
        private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
         
        public static void main(String[] args)  {
            final Test test = new Test();
             
            new Thread(){
                public void run() {
                    test.get(Thread.currentThread());
                };
            }.start();
             
            new Thread(){
                public void run() {
                    test.get(Thread.currentThread());
                };
            }.start();
             
        }  
         
        public synchronized void get(Thread thread) {
            long start = System.currentTimeMillis();
            while(System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName()+"正在进行读操作");
            }
            System.out.println(thread.getName()+"读操作完成");
        }
    }

       这段程序的输出结果会是,直到thread1运行完读操作之后,才会打印thread2运行读操作的信息。

    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0读操作完成
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1读操作完成

      而改成用读写锁的话:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class Test {
        private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
         
        public static void main(String[] args)  {
            final Test test = new Test();
             
            new Thread(){
                public void run() {
                    test.get(Thread.currentThread());
                };
            }.start();
             
            new Thread(){
                public void run() {
                    test.get(Thread.currentThread());
                };
            }.start();
             
        }  
         
        public void get(Thread thread) {
            rwl.readLock().lock();
            try {
                long start = System.currentTimeMillis();
                 
                while(System.currentTimeMillis() - start <= 1) {
                    System.out.println(thread.getName()+"正在进行读操作");
                }
                System.out.println(thread.getName()+"读操作完成");
            finally {
                rwl.readLock().unlock();
            }
        }
    }

       此时打印的结果为:

    复制代码
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-0读操作完成
    Thread-1读操作完成
    复制代码

      说明thread1和thread2在同一时候进行读操作。

      这样就大大提升了读操作的效率。

      只是要注意的是。假设有一个线程已经占用了读锁,则此时其它线程假设要申请写锁。则申请写锁的线程会一直等待释放读锁。

      假设有一个线程已经占用了写锁,则此时其它线程假设申请写锁或者读锁。则申请的线程会一直等待释放写锁。

      关于ReentrantReadWriteLock类中的其它方法感兴趣的朋友能够自行查阅API文档

    參考:http://www.cnblogs.com/dolphin0520/p/3923167.html


  • 相关阅读:
    JAVA中堆和栈的区别
    怎么回答面试官:你对Spring的理解?
    如何设计一个高可用、高并发秒杀系统
    这应该是把Java内存区域讲的最清楚的一篇文章
    Spring Cloud底层原理解析
    Spring事务管理详解
    选择合适Redis数据结构,减少80%的内存占用
    最强Java并发编程详解:知识点梳理,BAT面试题等
    深入理解HashMap
    Springboot 优雅停止服务的几种方法
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7366876.html
Copyright © 2011-2022 走看看