zoukankan      html  css  js  c++  java
  • 第二章:(2)Lock 接口

    一、什么是 Lock 接口

      1、Lock 接口介绍

        Lock 是 java.util.concurrent.locks 包中一个接口。

          java.util.concurrent.locks:为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。

          Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 关联对象。

        

         已知实现类:ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock

        这里使用 ReentrantLock,其他后面学习。

      2、Lock 实现可重入锁

      Lock 接口的实现类 ReentrantLock 可重入锁
      ReentrantLock 可重入锁实现了 Lock 接口
      

    二、Lock 实现卖票案例

      1、官方案例

    class X {
       private final ReentrantLock lock = new ReentrantLock();
       // ...
     
       public void m() {
         lock.lock();  // block until condition holds上锁
         try {
           // ... method body
         } finally {
           lock.unlock();   //解锁,释放资源
         }
       }
    }

      2、使用 Lock 锁

    //第一步  创建资源类,定义属性和和操作方法
    class LTicket {
        //票数量
        private int number = 30;
    
        //创建可重入锁
        private final ReentrantLock lock = new ReentrantLock(true);
    
        //卖票方法
        public void sale() {
            //上锁
            lock.lock();
            try {
                //判断是否有票
                if(number > 0) {
                    System.out.println(Thread.currentThread().getName()+" :卖出"+(number--)+" 剩余:"+number);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
            } finally {
                //锁【lock.lock】必须紧跟try代码块,且unlock要放到finally第一行。
                //解锁,无论是否有异常,都会释放锁
                lock.unlock();
            }
    
        }
    }
    
    public class LSaleTicket {
        public static void main(String[] args) {
            LTicket ticket = new LTicket();
            //第二步 创建多个线程,调用资源类的操作方法
            //创建三个线程
    
            new Thread(() -> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }, "AA").start();
    
            new Thread(() -> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }, "BB").start();
    
            new Thread(() -> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }, "CC").start();
        }
    }

      细节:调用 start() 方法会里面创建线程吗?

      不一定会立刻创建!

        private native void start0();
    

      可以看到最终是来调用操作系统的,是由操作系统来进行创建的。如果操作系统比较闲,可能就会立即创建了,如果很忙,可以会等待然后创建,取决于操作系统调度。

    三、Lock 与 Synchronized 区别(重点

      Lock 提供了比 synchronized 更多的功能。Lock 和 synchronized 有以下几点不同:

      1、 synchronized 是 Java 语言的关键字,synchronized 是内置的语言实现,因此是内置特性;

        Lock 不是 Java 语言内置的, Lock 是一个类,通过这个类可以实现同步访问;

      2、synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;

          而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;

        【synchronized 会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;】

      3、Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断;

        【用 synchronized 关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;】

      4、通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法判断是否获取锁的状态;

      5、Lock 可以提高多个线程进行读操作的效率;

      Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

      6、synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平可非公平(两者皆可);、

      7、Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

      在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized。

     

    四、Lock 接口

      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();
    }

      2、lock()/unlock()

      lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。

      采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用 Lock 必须在 try{} catch{}块中进行,并且将释放锁的操作放在finally 块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用 Lock来进行同步的话,是以下面这种形式去使用的:

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

      3、newCondition()

      关键字 synchronized 与 wait()/notify() 这两个方法一起使用可以实现等待/通知模式, Lock 锁的 newContition()方法返回 Condition 对象, Condition 类也可以实现等待/通知模式。

      用 notify()通知时,JVM 会随机唤醒某个等待的线程,使用 Condition 类可以进行选择性通知,Condition 比较常用的两个方法:

    • await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重新获得锁并继续执行。
    • signal()用于唤醒一个等待的线程。

      注意:在调用 Condition 的 await()/signal() 方法前,也需要线程持有相关的 Lock 锁,调用 await() 后线程会释放这个锁,在 singal() 调用后会从当前 Condition 对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦获得锁成功就继续执行。

      4、ReentrantLock 实现类

      ReentrantLock,意思是“可重入锁” ,关于可重入锁的概念将在后面学习。
      ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法。下面通过一些实例看具体看一下如何使用。

    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) {
                
            }finally {
                System.out.println(thread.getName()+"释放了锁");
                lock.unlock();
            }
        }
    }

     

      5、ReadWriteLock 接口

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

    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 接口。

      ReentrantReadWriteLock 里面提供了很多丰富的方法,不过最主要的有两个方法: readLock() 和 writeLock() 用来获取读锁和写锁。

      下面通过几个例子来看一下 ReentrantReadWriteLock 具体用法。

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

    public class Test {
        private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        public static void main(String[] args) {
            final Test test = new Test();
            new Thread(){
                @Override
                public void run() {
                    test.get(Thread.currentThread());
                };
            }.start();
            
            new Thread(){
                @Override
                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()+"读操作完毕");
        }
    }

      而改成用读写锁的话:

    public class Test {
      private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        public static void main(String[] args) {
            final Test test = new Test();
    
            new Thread(){
                @Override
                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();
            }
        }
    }

      说明 thread1 和 thread2 在同时进行读操作。这样就大大提升了读操作的效率。

      注意

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

     

  • 相关阅读:
    request.getParameter() 、 request.getInputStream()和request.getReader() 使用体会
    HTTP之Content-Length
    关于spring3中No Session found for current thread!and Transaction的配置和管理(转)
    Java数据类型和MySql数据类型对应一览
    Spring MVC 解读——View,ViewResolver(转)
    LeetCode 441. Arranging Coins
    LeetCode 415. Add Strings
    LeetCode 400. Nth Digit
    LeetCode 367. Valid Perfect Square
    LeetCode 326. Power of Three
  • 原文地址:https://www.cnblogs.com/niujifei/p/15815724.html
Copyright © 2011-2022 走看看