美文网首页
ReentrantLock 重入锁

ReentrantLock 重入锁

作者: larQ | 来源:发表于2018-10-26 00:16 被阅读0次

    ReentrantLock是jdk1.5里面的, 可以理解为相当于 Synchronized,但二者实现机制不一样,总之是 加锁,同步代码

    下面看一个示例

    public class Test {
        Lock lock = new ReentrantLock();
    
        void m1() {
            try {
                lock.lock(); //加锁
                for (int i = 0; i < 10; i++) {
                    System.out.println("m1() - " + i);
                    TimeUnit.SECONDS.sleep(1);
                }
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); //释放锁
            }
        }
    
        void m2() {
            lock.lock(); //加锁
            System.out.println("m2()");
            lock.unlock(); //释放锁
        }
    
        public static void main(String[] args) {
            final Test t = new Test();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    t.m1();
                }
            }).start();
    
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    t.m2();
                }
            }).start();
            
            //运行后可以看到 m1() 先打印10次 在打印没m2()
            //lock.lock() 到 lock.unlock() 之间的代码是同步的
        }
    
    }
    

    尝试锁

    直接上代码 注释在代码中

    public class Test {
        Lock lock = new ReentrantLock();
        
        void m1() {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("m1() - "+i);
            }
            lock.unlock();
        }
        
        void m2() {
            //尝试锁,尝试去获取锁
            boolean isLocked = false;
            try {
                //尝试锁, 如果有锁,无法获取锁标记,返回false。
                //如果获取锁标记,返回true
                //isLocked = lock.tryLock();
                
                // 阻塞尝试锁,阻塞参数代表的时长,尝试获取锁标记。
                // 如果超时,不等待。直接返回。
                //这里表示在5秒 内去尝试获取锁,如果能获取到返回true,否则返回false
                isLocked = lock.tryLock(5, TimeUnit.SECONDS); 
                
                if(isLocked) {
                    System.out.println("m2() is synchronized");
                }else {
                    System.out.println("m2() is unSynchronized");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                if(isLocked) {
                    // 尝试锁在解除锁标记的时候,一定要判断是否获取到锁标记。
                    // 如果当前线程没有获取到锁标记,会抛出异常。
                    //就是说 这里的if判断有必要写一下
                    lock.unlock();
                }
            }
        }
        
        public static void main(String[] args) {
            Test t = new Test();
            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    t.m1();
                }
            }).start();
            
            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    t.m2();
                }
            }).start();
        }
    }
    

    可打断

    阻塞状态: 包括普通阻塞,等待队列,锁池队列。

    • 普通阻塞: sleep(10000), 可以被打断。调用thread.interrupt()方法,可以打断阻塞状态,抛出InterruptedException异常。
    • 等待队列: wait()方法被调用,也是一种阻塞状态,只能由notify唤醒。无法打断
    • 锁池队列: 无法获取锁标记。不是所有的锁池队列都可被打断。
      ①使用ReentrantLock的lock方法,获取锁标记的时候,如果需要阻塞等待锁标记,无法被打断。
      ②使用ReentrantLock的lockInterruptibly方法,获取锁标记的时候,如果需要阻塞等待,可以被打断,打断后抛出InterruptedException异常。

    下面看一段代码

    public class Test {
        Lock lock = new ReentrantLock();
        
        void m1() {
            lock.lock();
            for (int i = 0; i < 5; i++) { //循环5次 每一秒 打印一下
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("m1() - "+ i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        
        void m2() {
            try {
                //也是加锁 但是 可尝试打断,阻塞等待锁。可以被其他的线程打断阻塞状态
                lock.lockInterruptibly();
                //lock.lock();
                System.out.println("m2()");
            } catch (InterruptedException e) {
                //被其他的线程打断后 进入到这个异常
                System.out.println("m2() interrupted");
            }finally {
                try{
                    //如果没有锁的话,执行lock.unlock(); 会抛出异常
                    lock.unlock();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
        
        public static void main(String[] args) {
            Test t = new Test();
            new Thread(new Runnable() { //
                @Override
                public void run() {
                    t.m1();
                }
            }).start();
            
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            //2秒后 启动t2线程
            Thread t2 = new Thread(new Runnable() {  //t2线程
                @Override
                public void run() {
                    t.m2();
                }
            });
            t2.start();
            
            //打断 。 非正常结束阻塞状态的线程,都会抛出异常。
            //如果m2() 中第一行 换成lock.lock(); 则不会抛出异常
            t2.interrupt();
        }
        
    }
    

    公平锁

    一般来说 多个线程去获取一把锁是属于竞争关系的,不管等的时间有多久。
    比如 t1 先获取到了锁,在运行阶段,t2,t3,t4都在阻塞等待,当t1释放锁后,那么t2,t3,t4回去竞争 那把锁,这个是随机的 ,不管哪个线程等的多久

    ReentrantLock 可以实现公平锁,实现等待时间长的线程先执行

    Synchronized没有公平性

    //定义公平锁 就是 加上 参数 true
    Lock lock = new ReentrantLock(true);
    

    这种比如 斗地主的 轮询等待时间

    ReentrantLock生产者 消费者 Condition

    public class Test<E> {  
        private final LinkedList<E> list = new LinkedList<>();
        private final int MAX = 10;
        private int count = 0;
        
        private Lock lock = new ReentrantLock();
        private Condition producer = lock.newCondition();
        private Condition consumer = lock.newCondition();
        
        public int getCount(){
            return count;
        }
        
        public void put(E e){
            lock.lock();
            try {
                while(list.size() == MAX){
                    System.out.println(Thread.currentThread().getName() + " 等待。。。");
                    // 进入等待队列。释放锁标记。
                    // 借助条件,进入的等待队列。
                    producer.await();
                }
                System.out.println(Thread.currentThread().getName() + " put 。。。");
                list.add(e);
                count++;
                // 借助条件,唤醒所有的消费者。
                consumer.signalAll();
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        
        public E get(){
            E e = null;
    
            lock.lock();
            try {
                while(list.size() == 0){
                    System.out.println(Thread.currentThread().getName() + " 等待。。。");
                    // 借助条件,消费者进入等待队列
                    consumer.await();
                }
                System.out.println(Thread.currentThread().getName() + " get 。。。");
                e = list.removeFirst();
                count--;
                // 借助条件,唤醒所有的生产者
                producer.signalAll();
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            } finally {
                lock.unlock();
            }
            
            return e;
        }
        
        public static void main(String[] args) {
            final Test<String> c = new Test<>();
            for(int i = 0; i < 10; i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j = 0; j < 5; j++){
                            System.out.println(c.get());
                        }
                    }
                }, "consumer"+i).start();
            }
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            for(int i = 0; i < 2; i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j = 0; j < 25; j++){
                            c.put("container value " + j); 
                        }
                    }
                }, "producer"+i).start();
            }
        }   
        
    }
    

    最后

    1,重入锁ReentrantLock,建议应用的同步方式,相对效率比synchronized高,量级较轻
    2,synchronized在jdk1.5开始尝试优化,到1.7版本级之后 效率已经非常好了,在绝对效率上 不比synchronized差多少
    3,使重入锁 ,必须手工释放锁标记,一般在finally中释放(unlock()方法)

    相关文章

      网友评论

          本文标题:ReentrantLock 重入锁

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