zoukankan      html  css  js  c++  java
  • Java多线程(七):ReentrantLock

    加锁和解锁

    我们来看下ReentrantLock的基本用法
    ThreadDomain35类

    public class ThreadDomain35 {
    
        private Lock lock = new ReentrantLock();
    
        public void testMethod()
        {
            try
            {
                lock.lock();
                for (int i = 0; i < 2; i++)
                {
                    System.out.println("ThreadName = " + Thread.currentThread().getName() + ", i  = " + i);
                }
            }
            finally
            {
                lock.unlock();
            }
        }
    }
    

    线程和main方法

    public class MyThread35 extends Thread {
    
        private ThreadDomain35 td;
    
        public MyThread35(ThreadDomain35 td)
        {
            this.td = td;
        }
    
        public void run()
        {
            td.testMethod();
        }
    
        public static void main(String[] args)
        {
            ThreadDomain35 td = new ThreadDomain35();
            MyThread35 mt0 = new MyThread35(td);
            MyThread35 mt1 = new MyThread35(td);
            MyThread35 mt2 = new MyThread35(td);
            mt0.start();
            mt1.start();
            mt2.start();
        }
    }
    

    输出结果

    ThreadName = Thread-2, i  = 0
    ThreadName = Thread-2, i  = 1
    ThreadName = Thread-0, i  = 0
    ThreadName = Thread-0, i  = 1
    ThreadName = Thread-1, i  = 0
    ThreadName = Thread-1, i  = 1
    

    一个线程必须执行完才能执行下一个线程,说明ReentrantLock可以加锁。

    ReentrantLock持有的对象监视器和synchronized不同

    ThreadDomain37类,methodB用synchronized修饰

    public class ThreadDomain37 {
        private Lock lock = new ReentrantLock();
    
        public void methodA()
        {
            try
            {
                lock.lock();
                System.out.println("MethodA begin ThreadName = " + Thread.currentThread().getName());
                Thread.sleep(5000);
                System.out.println("MethodA end ThreadName = " + Thread.currentThread().getName());
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            finally
            {
                lock.unlock();
            }
    
        }
    
        public synchronized void methodB()
        {
            System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
            System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
        }
    }
    

    MyThread37_0类

    public class MyThread37_0 extends Thread {
    
        private ThreadDomain37 td;
    
        public MyThread37_0(ThreadDomain37 td)
        {
            this.td = td;
        }
    
        public void run()
        {
            td.methodA();
        }
    }
    

    MyThread37_1类

    public class MyThread37_1 extends Thread {
        private ThreadDomain37 td;
    
        public MyThread37_1(ThreadDomain37 td)
        {
            this.td = td;
        }
    
        public void run()
        {
            td.methodB();
        }
    }
    

    MyThread37_main方法

    public class MyThread37_main {
    
        public static void main(String[] args)
        {
            ThreadDomain37 td = new ThreadDomain37();
            MyThread37_0 mt0 = new MyThread37_0(td);
            MyThread37_1 mt1 = new MyThread37_1(td);
            mt0.start();
            mt1.start();
        }
    
    }
    

    运行结果如下

    MethodA begin ThreadName = Thread-0
    MethodB begin ThreadName = Thread-1
    MethodB begin ThreadName = Thread-1
    MethodA end ThreadName = Thread-0
    

    加了synchronized依然是异步执行,说明ReentrantLock和synchronized持有的对象监视器不同。ReentrantLock需要手动加锁和释放锁。

    Condition

    基本用法

    synchronized与wait()和nitofy()/notifyAll()方法可以实现等待/唤醒模型,ReentrantLock同样可以,需要借助Condition的await()和signal/signalAll(),await()释放锁。
    ThreadDomain38类

    public class ThreadDomain38 {
        private Lock lock = new ReentrantLock();
        private Condition condition = lock.newCondition();
    
        public void await()
        {
            try
            {
                lock.lock();
                System.out.println("await时间为:" + System.currentTimeMillis());
                condition.await();
                System.out.println("await等待结束");
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            finally
            {
                lock.unlock();
            }
        }
    
        public void signal()
        {
            try
            {
                lock.lock();
                System.out.println("signal时间为:" + System.currentTimeMillis());
                condition.signal();
                System.out.println("signal等待结束");
            }
            finally
            {
                lock.unlock();
            }
        }
    }
    

    MyThread38类,线程和main方法

    public class MyThread38 extends Thread
    {
        private ThreadDomain38 td;
    
        public MyThread38(ThreadDomain38 td)
        {
            this.td = td;
        }
    
        public void run()
        {
            td.await();
        }
    
        public static void main(String[] args) throws Exception
        {
            ThreadDomain38 td = new ThreadDomain38();
            MyThread38 mt = new MyThread38(td);
            mt.start();
            Thread.sleep(3000);
            td.signal();
        }
    }
    

    运行结果如下

    await时间为:1563505465346
    signal时间为:1563505468345
    signal等待结束
    await等待结束
    

    可以看到,ReentrantLock和Condition实现了等待/通知模型。
    一个Lock可以创建多个Condition;
    notify()唤醒的线程是随机的,signal()可以有选择性地唤醒。

    Condition选择 唤醒/等待

    现在看一个利用Condition选择等待和唤醒的例子
    ThreadDomain47类,定义add和sub方法

    public class ThreadDomain47 {
        private final Lock lock = new ReentrantLock();
    
        private final Condition addCondition = lock.newCondition();
    
        private final Condition subCondition = lock.newCondition();
    
    
        private static int num = 0;
        private List<String> lists = new LinkedList<String>();
    
        public void add() {
            lock.lock();
    
            try {
                while(lists.size() == 10) {//当集合已满,则"添加"线程等待
                    addCondition.await();
                }
    
                num++;
                lists.add("add Banana" + num);
                System.out.println("The Lists Size is " + lists.size());
                System.out.println("The Current Thread is " + "增加线程");
                System.out.println("==============================");
                this.subCondition.signal();
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {//释放锁
                lock.unlock();
            }
        }
    
    
        public void sub() {
            lock.lock();
    
            try {
                while(lists.size() == 0) {//当集合为空时,"减少"线程等待
                    subCondition.await();
                }
    
                String str = lists.get(0);
                lists.remove(0);
                System.out.println("The Token Banana is [" + str + "]");
                System.out.println("The Current Thread is " + "减少线程");
                System.out.println("==============================");
                num--;
                addCondition.signal();
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    

    MyThread40_0类,增加线程

    public class MyThread40_0 implements Runnable {
    
        private ThreadDomain47 task;
    
        public MyThread40_0(ThreadDomain47 task) {
            this.task = task;
        }
    
        @Override
        public void run() {
            task.add();
        }
    
    }
    

    MyThread40_1类,减少线程

    public class MyThread40_1 implements Runnable {
        private ThreadDomain47 task;
    
        public MyThread40_1(ThreadDomain47 task) {
            this.task = task;
        }
    
        @Override
        public void run() {
            task.sub();
        }
    
    }
    

    main方法,启动线程

    public class MyThread40_main {
        public static void main(String[] args) {
            ThreadDomain47 task = new ThreadDomain47();
    
            Thread t1=new Thread(new MyThread40_0(task));
            Thread t3=new Thread(new MyThread40_0(task));
            Thread t7=new Thread(new MyThread40_0(task));
            Thread t8=new Thread(new MyThread40_0(task));
            Thread t2 = new Thread(new MyThread40_1(task));
            Thread t4 = new Thread(new MyThread40_1(task));
            Thread t5 = new Thread(new MyThread40_1(task));
            Thread t6 = new Thread(new MyThread40_1(task));
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            t5.start();
            t6.start();
            t7.start();
            t8.start();
        }
    }
    

    输出结果如下

    The Lists Size is 1
    The Current Thread is 增加线程
    ==============================
    The Lists Size is 2
    The Current Thread is 增加线程
    ==============================
    The Token Banana is [add Banana1]
    The Current Thread is 减少线程
    ==============================
    The Token Banana is [add Banana2]
    The Current Thread is 减少线程
    ==============================
    The Lists Size is 1
    The Current Thread is 增加线程
    ==============================
    The Token Banana is [add Banana1]
    The Current Thread is 减少线程
    ==============================
    The Lists Size is 1
    The Current Thread is 增加线程
    ==============================
    The Token Banana is [add Banana1]
    The Current Thread is 减少线程
    ==============================
    

    可以看到,lists的数量不会增加太多,也不会减少太多。当集合满,使增加线程等待,唤醒减少线程;当集合空,使减少线程等待,唤醒增加线程。我们用wait()/notify()机制无法实现该效果,这里体现了Condition的强大之处。

    ReentrantLock中的方法

    公平锁和非公平锁
    ReentrantLock可以指定公平锁和非公平锁,公平锁根据线程运行的顺序获取锁,非公平锁则通过抢占获得锁,不按线程运行顺序。synchronized是非公平锁。在ReentrantLock(boolean fair)构造函数传入true/false来指定公平锁/非公平锁。
    看个例子
    ThreadDomain39类和main方法

    public class ThreadDomain39 {
        private Lock lock = new ReentrantLock(true);
    
        public void testMethod()
        {
            try
            {
                lock.lock();
                System.out.println("ThreadName" + Thread.currentThread().getName() + "获得锁");
            }
            finally
            {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws Exception
        {
            final ThreadDomain39 td = new ThreadDomain39();
            Runnable runnable = new Runnable()
            {
                public void run()
                {
                    System.out.println("线程" + Thread.currentThread().getName() + "运行了");
                    td.testMethod();
                }
            };
            Thread[] threads = new Thread[5];
            for (int i = 0; i < 5; i++)
                threads[i] = new Thread(runnable);
            for (int i = 0; i < 5; i++)
                threads[i].start();
        }
    }
    

    输出结果如下

    线程Thread-0运行了
    ThreadNameThread-0获得锁
    线程Thread-1运行了
    线程Thread-2运行了
    ThreadNameThread-1获得锁
    线程Thread-3运行了
    线程Thread-4运行了
    ThreadNameThread-2获得锁
    ThreadNameThread-3获得锁
    ThreadNameThread-4获得锁
    

    可以看到公平锁获得锁的顺序和线程运行的顺序相同。公平锁尽可能地让线程获取锁的顺序和线程运行顺序保持一致,再执行几次,可能不一致。
    ReentrantLock构造函数传入false,输出结果如下:

    线程Thread-0运行了
    线程Thread-2运行了
    线程Thread-4运行了
    线程Thread-3运行了
    ThreadNameThread-0获得锁
    线程Thread-1运行了
    ThreadNameThread-1获得锁
    ThreadNameThread-2获得锁
    ThreadNameThread-4获得锁
    ThreadNameThread-3获得锁
    

    非公平锁获得锁的顺序和线程运行的顺序不同

    getHoldCount()

    获取当前线程调用lock()的次数,一般debug使用。
    看个例子

    public class ThreadDomain40 {
        private ReentrantLock lock = new ReentrantLock();
    
        public void testMethod1()
        {
            try
            {
                lock.lock();
                System.out.println("testMethod1 getHoldCount = " + lock.getHoldCount());
                testMethod2();
            }
            finally
            {
                lock.unlock();
            }
        }
    
        public void testMethod2()
        {
            try
            {
                lock.lock();
                System.out.println("testMethod2 getHoldCount = " + lock.getHoldCount());
            }
            finally
            {
                lock.unlock();
            }
        }
    
    
    
        public static void main(String[] args)
        {
            ThreadDomain40 td = new ThreadDomain40();
            td.testMethod1();
        }
    
    
    }
    

    输出结果如下

    testMethod1 getHoldCount = 1
    testMethod2 getHoldCount = 2
    

    可以看到,testMethod1()被调用了一次,testMethod2()被调用了两次,ReentrantLock和synchronized一样,锁都是可重入的。

    getQueueLength()和isFair()

    getQueueLength()获取等待的线程数量,isFair()判断是否是公平锁。
    ThreadDomain41类和main方法,Thread.sleep(2000)使第一个线程之后的线程都来不及启动,Thread.sleep(Integer.MAX_VALUE)使线程无法unlock()。

    public class ThreadDomain41 {
        public ReentrantLock lock = new ReentrantLock();
    
        public void testMethod()
        {
            try
            {
                lock.lock();
                System.out.println("ThreadName = " + Thread.currentThread().getName() + "进入方法!");
                System.out.println("是否公平锁?" + lock.isFair());
                Thread.sleep(Integer.MAX_VALUE);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            finally
            {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws InterruptedException
        {
            final ThreadDomain41 td = new ThreadDomain41();
            Runnable runnable = new Runnable()
            {
                public void run()
                {
                    td.testMethod();
                }
            };
            Thread[] threads = new Thread[10];
            for (int i = 0; i < 10; i++)
                threads[i] = new Thread(runnable);
            for (int i = 0; i < 10; i++)
                threads[i].start();
            Thread.sleep(2000);
            System.out.println("有" + td.lock.getQueueLength() + "个线程正在等待!");
        }
    }
    

    输出结果如下

    ThreadName = Thread-1进入方法!
    是否公平锁?false
    有9个线程正在等待!
    

    ReentrantLock默认是非公平锁,只有一个线程lock(),9个线程在等待。

    hasQueuedThread()和hasQueuedThreads()

    hasQueuedThread(Thread thread)查询指定线程是否在等待锁,hasQueuedThreads()查询是否有线程在等待锁。
    看个例子
    ThreadDomain41类和main方法,和上面例子类似,Thread.sleep(Integer.MAX_VALUE); 让线程不释放锁,Thread.sleep(2000);让第一个线程之后的线程都无法启动。

    public class ThreadDomain42 extends ReentrantLock {
        public void waitMethod()
        {
            try
            {
                lock();
                Thread.sleep(Integer.MAX_VALUE);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            finally
            {
                unlock();
            }
        }
    
        public static void main(String[] args) throws InterruptedException
        {
            final ThreadDomain42 td = new ThreadDomain42();
            Runnable runnable = new Runnable()
            {
                public void run()
                {
                    td.waitMethod();
                }
            };
            Thread t0 = new Thread(runnable);
            t0.start();
            Thread.sleep(500);
            Thread t1 = new Thread(runnable);
            t1.start();
            Thread.sleep(500);
            Thread t2 = new Thread(runnable);
            t2.start();
            Thread.sleep(500);
            System.out.println("t0 is waiting?" + td.hasQueuedThread(t0));
            System.out.println("t1 is waiting?" + td.hasQueuedThread(t1));
            System.out.println("t2 is waiting?" + td.hasQueuedThread(t2));
            System.out.println("Is any thread waiting?" + td.hasQueuedThreads());
        }
    }
    

    输出结果如下

    t0 is waiting?false
    t1 is waiting?true
    t2 is waiting?true
    Is any thread waiting?true
    

    t0线程获得了锁,t0没有释放锁,导致t1,t2等待锁。

    isHeldByCurrentThread()和isLocked()

    isHeldByCurrentThread()判断锁是否由当前线程持有,isLocked()判断锁是否由任意线程持有。
    请看示例
    ThreadDomain43类和main方法

    public class ThreadDomain43 extends ReentrantLock {
        public void testMethod()
        {
            try
            {
                lock();
                System.out.println(Thread.currentThread().getName() + "线程持有了锁!");
                System.out.println(Thread.currentThread().getName() + "线程是否持有锁?" +
                        isHeldByCurrentThread());
                System.out.println("是否任意线程持有了锁?" + isLocked());
            } finally
            {
                unlock();
            }
        }
    
        public void testHoldLock()
        {
            System.out.println(Thread.currentThread().getName() + "线程是否持有锁?" +
                    isHeldByCurrentThread());
            System.out.println("是否任意线程持有了锁?" + isLocked());
        }
    
        public static void main(String[] args)
        {
            final ThreadDomain43 td = new ThreadDomain43();
            Runnable runnable0 = new Runnable()
            {
                public void run()
                {
                    td.testMethod();
                }
            };
            Runnable runnable1 = new Runnable()
            {
                public void run()
                {
                    td.testHoldLock();
                }
            };
            Thread t0 = new Thread(runnable0);
            Thread t1 = new Thread(runnable1);
            t0.start();
            t1.start();
        }
    }
    

    输出结果如下

    Thread-0线程持有了锁!
    Thread-1线程是否持有锁?false
    Thread-0线程是否持有锁?true
    是否任意线程持有了锁?true
    是否任意线程持有了锁?true
    

    Thread-0线程testMethod方法持有锁,Thread-1线程testHoldLock方法没有lock操作,所以不持有锁。

    tryLock()和tryLock(long timeout, TimeUnit unit)

    tryLock()有加锁的功能,获得了锁且锁没有被另外一个线程持有,此时返回true,否则返回false,可以有效避免死锁。tryLock(long timeout, TimeUnit unit)表示在给定的时间内获得了锁,锁没有被其他线程持有,且不处于中断状态。返回true,否则返回false;
    看个例子

    public class MyThread39 {
        public static void main(String[] args) {
    
            System.out.println("开始");
            final Lock lock = new ReentrantLock();
            new Thread() {
                @Override
                public void run() {
                    String tName = Thread.currentThread().getName();
                    if (lock.tryLock()) {
                        System.out.println(tName + "获取到锁!");
                    } else {
                        System.out.println(tName + "获取不到锁!");
                        return;
                    }
                    try {
                        for (int i = 0; i < 5; i++) {
                            System.out.println(tName + ":" + i);
                        }
                        Thread.sleep(5000);
                    } catch (Exception e) {
                        System.out.println(tName + "出错了!");
                    } finally {
                        System.out.println(tName + "释放锁!");
                        lock.unlock();
                    }
    
                }
            }.start();
    
            new Thread() {
                @Override
                public void run() {
                    String tName = Thread.currentThread().getName();
    
                    try {
                        if (lock.tryLock(1,TimeUnit.SECONDS)) {
                            System.out.println(tName + "获取到锁!");
                        } else {
                            System.out.println(tName + "获取不到锁!");
                            return;
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    try {
                        for (int i = 0; i < 5; i++) {
                            System.out.println(tName + ":" + i);
                        }
    
                    } catch (Exception e) {
                        System.out.println(tName + "出错");
                    } finally {
                        System.out.println(tName + "释放锁!");
                        lock.unlock();
                    }
                }
            }.start();
    
            System.out.println("结束");
        }
    }
    

    输出结果如下

    开始
    Thread-0获取到锁!
    Thread-0:0
    Thread-0:1
    Thread-0:2
    Thread-0:3
    Thread-0:4
    结束
    Thread-1获取不到锁!
    Thread-0释放锁!
    

    Thread-0先获得了锁,且sleep了5秒,导致Thread-1获取不到锁,我们给Thread-1的tryLock设置1秒,一秒内获取不到锁就会返回false。
    如果Thread.sleep(0),那么Thread-0和Thread-1都可以获得锁,园友可以自己试下。

    synchronized和ReentrantLock的比较

    1.synchronized关键字是语法层面的实现,ReentrantLock要手动lock()和unlock();
    2.synchronized是不公平锁,ReentrantLock可以指定是公平锁还是非公平锁;
    3.synchronized等待/唤醒机制是随机的,ReentrantLock借助Condition的等待/唤醒机制可以自行选择等待/唤醒;

  • 相关阅读:
    10003 Cutting Sticks(区间dp)
    Cocos2d-x init() 和 onEnter() 区别
    HDU1181【有向图的传递闭包】
    空间参考系统与WKT解析
    面试经典-分金条
    uvalive 3971
    lua学习:使用Lua处理游戏数据
    面试经典--两个房间 每间房间三盏灯
    浙江大学PAT上机题解析之2-11. 两个有序链表序列的合并
    顺序队列之C++实现
  • 原文地址:https://www.cnblogs.com/Java-Starter/p/11212079.html
Copyright © 2011-2022 走看看