美文网首页
多线程高并发编程之ReentrantLock

多线程高并发编程之ReentrantLock

作者: Minority | 来源:发表于2020-03-05 15:37 被阅读0次

    以下代码均通过使线程睡眠模拟实际业务场景来解释原理

    Case1(reentrantlock替代synchronized):

    package ReentrantLock;
    import java.util.concurrent.TimeUnit;
    
    class ReentrantLock1 {
        synchronized void m1(){
            for (int i = 0; i < 10; i++) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
        }
    
        synchronized void m2(){
            System.out.println("m2.....");
        }
    
        public static void main(String[] args) {
            ReentrantLock1 r1=new ReentrantLock1();
            new Thread(r1::m1).start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(r1::m2).start();
        }
    }
    /*====================output========================
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    m2.....
    */
    

    使用synchronized 编程时,写法如上,上例中由于m1锁定this,只有m2执行完毕的时候,m2才能执行。下面使用ReentrantLock来替代synchronized :

    package ReentrantLock;
    
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class ReentrantLock2 {
    
        //Lock是ReentrantLock实现的接口
        Lock lock=new ReentrantLock();
    
        void m1(){
           
            lock.lock();    //相当于synchronized(this)
            try {
                for (int i = 0; i < 10; i++) {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {                          //finally一定得写,用来手工释放锁
                lock.unlock();
            }
        }
    
        void m2(){
            lock.lock();
            try {
                System.out.println("m2....");
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            ReentrantLock2 r1=new ReentrantLock2();
            //映射方式start
            //new Thread(r1::m1).start();
    
            /*lambda表达式写法
            new Thread(()->{
                r1.m1();
            },"t1").start();
            */
            
            //普通写法
            new Thread(new Runnable(){
    
                @Override
                public void run() {
                    r1.m1();
                }
            }).start();
    
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(r1::m2).start();
        }
    }
    

    Lock是ReentrantLock实现的接口,本例的结果和上面使用synchronized 得到的结果相同。reentrantlock是可以用于替代synchronized的锁(手动上锁,手动释放)

    知识点:

    • 需要注意的是,必须要手动释放锁(非常重要,ReentrantLock是手工锁
    • 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放

    Case2(ReentrantLock之trylock):

    package ReentrantLock
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class ReentrantLock3 {
        Lock lock=new ReentrantLock();
    
        void m1(){
    
            lock.lock();    //相当于synchronized(this)
            try {
                for (int i = 0; i < 10; i++) {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    
    
        void m2(){
    
    
            /*第一种方法:lock.tryLock(),若拿着锁返回true,否则返回false
            *
            boolean locked=lock.tryLock();
            System.out.println("m2..."+locked);
            if (locked) lock.unlock();*/
    
            boolean locked=false;
            try {
                //第二种用法:尝试等n秒,如果还是没有拿到锁,则该干嘛干嘛
                locked=lock.tryLock(5,TimeUnit.SECONDS);
                System.out.println("m2...."+locked);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (locked) lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            ReentrantLock3 r1=new ReentrantLock3();
            new Thread(r1::m1).start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(r1::m2).start();
        }
    }
    

    使用reentrantlock可以进行“尝试锁定”trylock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待。所以,reentrantlock比synchronized更加灵活,其能进行等待获取锁。在效率上,两者没有什么区别啦(对synchronized进行底层优化后,以前的synchronized锁的粒度是比较重的),而且,还能指定reentrantlock锁的公平性

    知识点:

    • 使用trylock进行尝试锁定,不管锁定与否,方法都将继续执行
    • reentrantlock有两种使用方式:可以根据trylock的返回值来判断是否锁定,也可以指定trylock的时间。lock.tryLock()可以根据对返回的布尔值的判断来继续执行,而不会抛出异常。但指定时间时,由于trylock(time)会抛出异常,所以要注意unlock的处理,必须方法finally中
      1.第一种方法:lock.tryLock(),若拿着锁返回true,否则返回false
          boolean locked=lock.tryLock();
          System.out.println("m2..."+locked);
          if (locked)   lock.unlock(); 
          else   ....
      
      1. 第二种用法:尝试等n秒,如果还是没有拿到锁,则该干嘛干嘛
      boolean locked=lock.tryLock();
      try {
              locked=lock.tryLock(5,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
              e.printStackTrace();
        } finally {
               if (locked) lock.unlock();
        }
      

    Case3(ReentrantLock之lockInterruptibly):

    package ReentrantLock;
    
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLock4 {
        public static void main(String[] args) {
            Lock lock=new ReentrantLock();
    
            Thread t1=new Thread(()->{
                lock.lock();
                try {
                    System.out.println("t1 start");
                    //t1一直运行,其他等待lock的线程永远等待
                    TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
                    System.out.println("t1 end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            });
    
            t1.start();
    
            Thread t2=new Thread(()->{
    
                try {
                    //如果使用lock.lock();则t2会永远等待,并不能打断t2的执行
                    //lock.lock();
    
                    //lock.lockInterruptibly();可以在等不到资源的情况下把该线程(t2)打断,不再执行
                    //注意:t2是不可能打断t1的,没这个权利。所谓的打断就是结束等待线程
                    lock.lockInterruptibly(); //可以对interrupt()做出响应
                    System.out.println("t2 start");
                    TimeUnit.SECONDS.sleep(5);
                    System.out.println("t2 end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
    
            });
    
            t2.start();
    
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            t2.interrupt();//打断线程2的等待
        }
    }
    /*====================output========================
    t1 start
    t2结束等待
    java.lang.InterruptedException
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
        at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
        at ReentrantLock.ReentrantLock4.lambda$main$1(ReentrantLock4.java:40)
        at java.lang.Thread.run(Thread.java:745)
    Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
        at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
        at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
        at ReentrantLock.ReentrantLock4.lambda$main$1(ReentrantLock4.java:48)
        at java.lang.Thread.run(Thread.java:745)
    */
    

    使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断。

    上面的例子中,首先new出一个ReentrantLock,t1获得lock锁时,进行永久睡眠。紧接着t2通过lock.lockInterruptibly();尝试着获得锁,由于锁被t1占着,所以t2获得锁失败,就一直等待。这时,调用t2.interrupt();就可以直接把t2线程打断,t2不在等待,而是直接结束线程。

    注意:

    • lock.lockInterruptibly();可以在等不到资源的情况下把该线程(t2)打断,不再执行
    • 注意:t2是不可能打断t1的,没这个权利。所谓的打断就是结束等待线程

    Case4(ReentrantLock之公平锁):

    package ReentrantLock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    
    //本身是从Thread继承的,所以不用再实现runnable接口
    public class ReentrantLock5 extends Thread {
    
        //公平锁t1和t2交换执行
        private static ReentrantLock lock=new ReentrantLock(true);//参数为true表示为公平锁,默认是非公平的锁。请对比输出结果
    
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName()+"获得锁");
                } finally {
                    lock.unlock();
                }
            }
        }
    
        public static void main(String[] args) {
            ReentrantLock5 r1=new ReentrantLock5();
            Thread th1=new Thread(r1);
            Thread th2=new Thread(r1);
            th1.start();
            th2.start();
        }
    }
    /*====================output========================
    Thread-1获得锁
    Thread-2获得锁
    Thread-1获得锁
    Thread-2获得锁
    Thread-1获得锁
    Thread-2获得锁
    Thread-1获得锁
    。。。。。。
    */
    

    默认的synchronized是非公平锁,并不关心谁等的时间长,由线程调度器来进行调度,ReentrantLock还可以指定公平锁。就是谁等的时间长就让谁来获得这把锁。

    使用private static ReentrantLock lock=new ReentrantLock(true);创建ReentrantLock对象时,参数为true表示为公平锁,默认是非公平的锁。从执行的结果可以看出,t1和t2是交替执行的,如果把公平锁的参数设为false,那么结果将是其中一个线程执行完,另一个再执行。



    参考:马士兵老师java多线程高并发编程

    相关文章

      网友评论

          本文标题:多线程高并发编程之ReentrantLock

          本文链接:https://www.haomeiwen.com/subject/zexfrhtx.html