zoukankan      html  css  js  c++  java
  • java并发学习01 --- Reentrantlock 和 Condition

    本文是笔者看了《实战java高并发程序设计》之后加上自己的理解所写的笔记。

    之所以直接从并发工具开始,是因为多线程的基础知识,例如多线程创建,常用的方法,以及synchronized,volatile关键字等知识之前学习的时候已经学习过许多遍了,但是java并发包却鲜有接触,这次决定写成博客,系列的总结一下。(之后有时间的话再把前面的知识补充上来,让这个系列更完整一些)

    Reentrantlock(重入锁) 

    简单看一下它的用法:

    重入锁基本代码演示:

    package thread.thread_util;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 展示重入锁的用法
     */
    public class Lesson16_ReetrantLock {
        public static void main(String[] args) {
            Runnable target = new Runnable() {
                private Lock lock = new ReentrantLock();
                @Override
                public void run() {
                    lock.lock();
    //                lock.lock();
                    try {
                        for (int i = 0; i < 5 ; i++) {
                            System.out.println(Thread.currentThread().getId() + ": " + i);
                        }
                    } finally {
                        lock.unlock();
    //                    lock.unlock();
                    }
                }
            };
            Thread t1 = new Thread(target);
            Thread t2 = new Thread(target);
            t1.start();
            t2.start();
        }
    }

    重入到底是什么意思?

    相比synchronized关键字,重入锁显示的调用了加锁和解锁的时机,但是为什么要叫重入锁呢?重入二字是什么意思呢?

    重入的意思就是同一个线程可以反复进入同一个锁,上面的代码中就算把注释给去掉代码也是可以执行的。

    当然这个代码例子仍然不够贴切,谁会没事加两把锁上去呢?但是你可以想象一下递归,我们都知道递归的每次操作都把相关的变量压入了一个栈之中,执行完成就弹出栈,然后再执行上一层函数,这里如果我们进行了加锁操作的话,那么每一层函数在递推的时候就都会加上一层锁,而在回归的时候函数结束,则会释放锁。重入锁的内部有一个计数器,每加一个锁,计数器就加1,释放一个锁,计数器就减1,只有当计数器为0时,锁才能被释放。

    我们以经典的斐波那契数作为例子来展示一下。

    深入理解“重入”代码演示:

    package thread.thread_util;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 递归中的重入锁
     */
    public class Lesson16_ReentrantLock02 {
        public static void main(String[] args) {
            Runnable target = new Runnable() {
                private Lock lock = new ReentrantLock();
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getId());
                    System.out.println("最终结果为: " + fibonacci(3));
                }
    
                public int fibonacci (int n) {
                    try {
                        lock.lock();
                        System.out.println(Thread.currentThread().getId() + "获得了锁");
                        if( n == 1 ) return 1;
                        if( n == 2 ) return 1;
                        return fibonacci(n-1) + fibonacci(n-2);
                    } finally {
                        lock.unlock();
                        System.out.println(Thread.currentThread().getId() + "释放了锁");
                    }
                }
            };
            Thread thread = new Thread(target);
            thread.start();
        }
    }

    结果:

    11
    11获得了锁
    11获得了锁
    11释放了锁
    11获得了锁
    11释放了锁
    11释放了锁
    最终结果为: 2

    我们可以看到这里总共获得了3次锁,释放了3次锁,整个锁才会被最终释放。

    你可以再开一个线程进行验证,结果会类似下面这样:

    11
    11获得了锁
    11获得了锁
    11释放了锁
    11获得了锁
    11释放了锁
    11释放了锁
    最终结果为: 2
    12
    12获得了锁
    12获得了锁
    12释放了锁
    12获得了锁
    12释放了锁
    12释放了锁
    最终结果为: 2

    重入锁的好基友:Condition对象

     Condition的功能和Object.wait()与Object.notify()比较相似,它让我们可以控制某个线程什么时候开始等待,什么时候被唤醒,而不是由jvm来决定。

     条件对象代码演示:

    package thread.thread_util;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 展示条件对象的基本用法
     * await
     * signal
     * signalAll
     */
    public class Lesson17_Condition01 implements Runnable{
        public static Lock lock = new ReentrantLock();
        public static Condition condition = lock.newCondition();
        @Override
        public void run() {
            try {
                lock.lock();
                condition.await();
                System.out.println("Thread is going on");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        public static void main(String[] args) throws Exception{
            Runnable target = new Lesson17_Condition01();
            Thread thread = new Thread(target);
            thread.start();
            Thread.sleep(2000);
    
            lock.lock();
            condition.signal();
            lock.unlock();
        }
    }

    这里在Runnable对象中,将对象锁住并让线程进入了休眠状态

    主线程中进行了2秒钟的休眠,这2秒钟内子线程也无法执行,因为被“await()”了,直到2秒钟后,主线程再次开始执行,对线程进行了唤醒(signal)操作,唤醒操作也要进行加锁操作。

    关于重入锁还有几点需要说明:

    • 中断响应:对于synchronized来说,线程只有两种状态,要么获得锁开始执行,要么在队列中等待。

              而对于重入锁来说,还存在另外一种状态,那就是中断线程,也就是把等待队列中的线程中断,那么这个中断有什么用呢?

              比如说如果出现了死锁的情况,两个线程都在等待对方释放资源,那么这个时候如果我主动的中断一个线程,那么这个线程所持有的资源就被释放啦。我们来看看代码

     重入锁中断响应代码演示:

    package thread.thread_util;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 展示重入锁的中断响应功能
     * 首先构造一个死锁
     * 然后中断某个线程,释放资源
     * 最后另外一个线程就可以获得释放的资源,完成线程的任务
     */
    public class Lesson16_ReentrantLock03 implements Runnable{
        private static ReentrantLock lock1 = new ReentrantLock();
        private static ReentrantLock lock2 = new ReentrantLock();
        private int flag = 0;
    
        public void setFlag(int val) {
            this.flag = val;
        }
        @Override
        public void run() {
    
            try {
                if(flag == 1) {
                    lock1.lockInterruptibly();
                    Thread.sleep(1000);
                    lock2.lockInterruptibly();
                    System.out.println("Thread1 is going on ");
                }
                else {
                    lock2.lockInterruptibly();
                    Thread.sleep(1000);
                    lock1.lockInterruptibly();
                    System.out.println("Thread2 is going on ");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if(lock1.isHeldByCurrentThread()){
                    lock1.unlock();
                }
                if(lock2.isHeldByCurrentThread()){
                    lock2.unlock();
                }
            }
        }
    
        public static void main(String[] args) {
            Lesson16_ReentrantLock03 target1 = new Lesson16_ReentrantLock03();
            Lesson16_ReentrantLock03 target2 = new Lesson16_ReentrantLock03();
    
            target1.setFlag(1);
            target2.setFlag(2);
    
            Thread thread1 = new Thread(target1);
            Thread thread2 = new Thread(target2);
            thread1.start();
            thread2.start();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread2.interrupt();
        }
    }
    • lockInterruptibly  : 虽然锁住了对象,但是可以响应中断,发现中断之后就释放锁

           Acquires the lock unless the current thread is interrupted. (文档说明)

    最后线程2进行了中断,代码中的“else”里面的代码中的lockInterruptibly()方法就会使线程2 释放lock2并放弃对lock1的请求。

    运行结果:

    Thread1 is going on 
    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 thread.thread_util.Lesson16_ReentrantLock03.run(Lesson16_ReentrantLock03.java:33)
        at java.lang.Thread.run(Thread.java:748)
    
    Process finished with exit code 0

    锁申请等待时限(tryLock)

    这是主动的中断,还有另外一种更为简便的方法,就是给锁的申请添加时限,如果规定时间内我还没得到锁,那我就不要这把锁了

    trylock() 代码演示:

    package thread.thread_util;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Lesson16_ReentrantLock04 implements Runnable{
        private static ReentrantLock lock = new ReentrantLock();
        @Override
        public void run () {
            try {
                if(lock.tryLock(3,TimeUnit.SECONDS)){
                    System.out.println(Thread.currentThread().getName() + " get lock successful");
                    Thread.sleep(4000);
                } else {
                    System.out.println(Thread.currentThread().getName() + " get lock failed");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            Lesson16_ReentrantLock04 target = new Lesson16_ReentrantLock04();
            Thread t1 = new Thread(target);
            Thread t2 = new Thread(target);
            t1.setName("线程1");
            t2.setName("线程2");
            t1.start();
            t2.start();
        }
    }

    这里线程1先获得了锁,然后睡眠6秒钟(sleep并不会释放锁),所以线程2在3秒钟内都无法获得所最后进入else代码段。

    最后结果:

    线程1 get lock successful
    线程2 get lock failed

    关于重入锁和条件对象暂时就写到这里。

  • 相关阅读:
    CHAR和HEX互相转换
    Delphi之TComponent类
    Delphi 延迟函数 比sleep 要好的多
    Delphi中三种延时方法及其定时精度分析
    Cport 应用集合
    重命名数据库时提示无法用排他锁锁定数据库
    Bugzilla在XP下安装
    Web service 超过了最大请求长度
    调用webservice时提示对操作的回复消息正文进行反序列化时出错
    c# IL 指令解析
  • 原文地址:https://www.cnblogs.com/bax-life/p/9931183.html
Copyright © 2011-2022 走看看