ReenTrantLock小记

Lock(锁)是对运行资源的锁定。RenentrantLock(可重入锁)是锁的一种实现了Lock接口和Serializable接口。通过维护一个实现AbstractQueuedSynchronizer接口的内部类RenentrantLock$Sync来实现锁的各个功能。
接下来的对于锁的描述针对RenentrantLock(可重入锁)。
锁有公平锁和非公平锁:

公平锁按照争夺顺序,先争夺的线程在资源空置时先获得资源。而非公平锁则是资源一但空置,所有争夺线程都去争夺资源,谁争到资源各凭本事(见代码LockIsFair)。
这些争夺线程所存放的争夺队列是以链表方式(记录头head与尾tail,初始化为懒汉式)由AbstractQueuedSynchronizer维护并实现。

代码LockIsFair:

package ReenTrantLock;

import java.util.concurrent.locks.ReentrantLock;

public class LockIsFair {

    public static void main(String[] args) {
        final ReentrantLock r = new ReentrantLock(true); // 无参时为非公平锁模式,入参为true时为公平锁模式
        
        for (int i = 0; i < 10; i++) {
            final int j = i;
            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    System.out.println(j + "开始竞争");
                    r.lock();
                    System.out.println(j + "争到锁");
                    r.unlock();
                    System.out.println(j + "释放锁");
                }
            }).start();
        }

    }

}

运行可以得到:

非公平锁模式:

1开始竞争
3开始竞争
2开始竞争
0开始竞争
5开始竞争
4开始竞争
1争到锁
7开始竞争
6开始竞争
7争到锁
7释放锁
8开始竞争

可以看到7后竞争但是却先拿到锁。

lock(),lockInterruptibly(),tryLock()之间的区别:

lock: 不考虑中断的等待锁,如果线程被中断会抛出异常java.lang.InterruptedException: sleep interrupted。不要求调用者要捕获此方法可能抛出的中断异常进行处理

lockInterruptibly:考虑中断的等待锁,如果线程被中断会抛出异常java.lang.InterruptedException。此方法因为throws InterruptedException,因此要求调用者要对此方法可能抛出的中断异常进行处理

tryLock:考虑中断的在指定时间等待锁,如果线程被中断会抛出异常java.lang.InterruptedException。此方法因为throws InterruptedException,因此要求调用者要对此方法可能抛出的中断异常进行处理

代码:LockAndLockInterruptibly

package ReenTrantLock;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;


public class LockAndLockInterruptibly {


    public static void main(String[] args) {
        final ReentrantLock lock = new ReentrantLock();
        final Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println(1);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("1error");
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });
        
        Thread t2 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    lock.lock();    // 等待锁,不考虑中断
                    lock.lockInterruptibly(); // 等待锁,考虑中断,需对中断进行处理
                    lock.tryLock(2000,TimeUnit.SECONDS);    // 等待锁,考虑中断,需对中断进行处理
                    Thread.sleep(100);
                    System.out.println(2);
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    System.out.println("2error");
                    e.printStackTrace();    // 这个地方打出来的异常有些许不同
                                            // lock 打出的异常为java.lang.InterruptedException: sleep interrupted
                                            // lockInterruptibly 打出的异常为java.lang.InterruptedException:
                                            // tryLock 打出的异常为java.lang.InterruptedException:
                } finally {
                    lock.unlock();
                }
            }
        });
        t1.start();
        t2.start();
        t2.interrupt();
    }
}

Condition:

如果要人为的控制线程对资源的争夺则需要线程间的通信,控制线程等待以及多个线程之间的通信则通过Condition接口来实现,可以通过ReentrantLock.newCondition()来获得一个Condition实例。
值得注意的是一个Condition代表一个通信通道,一个condition的等待指令只能通过同一个condition才能唤醒。不同Condition之间是无法交叉唤醒的(见代码ConditionDemo)。
这是因为Condition的等待唤醒机制:每个Condition会维护一个属于自己的等待队列(链表方式,记录firstWaiter和lastWaiter,由AbstractQueuedSynchronizer$ConditionObject维护并实现,
也正是这个机制使得唤醒时按照等待顺序先唤醒先等待的线程即firstWaiter),里面存放着等待自己唤醒(由自己使其等待)的线程。
wait与sleep不同,wait时系统等待并释放资源,sleep时线程睡眠不会释放线程。
ReentrantLock的几种wait方法(见代码ConditionDemo):
c.await();    // 等待
c.await(1, TimeUnit.SECONDS); //等待1s, 1s后解除等待开始竞争资源
c.awaitNanos(1000L);// 等待1000纳秒  
c.awaitUntil(new Date(new Date().getTime() + 1000)); // 等待到某一日期
c.awaitUninterruptibly();   // 无中断的等待,在这个等待状态下,此线程会无视对他的中断指令

代码ConditionDemo:

package ReenTrantLock;

import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionDemo {

    public static void main(String[] args) {
        final ReentrantLock lock = new ReentrantLock();
        final Condition c = lock.newCondition();

        /*---------------多个线程之间的相互唤醒------------------*/
        /**
        for (int i = 0; i < 10; i++) {
            final int j = i+10;
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        lock.lock();
                        System.out.println(j + "拿到锁");
                        System.out.println(j + "等待信号");
                        c.await(); 
                        System.out.println(j + "收到信号");
                        System.out.println(j + "发出信号");
                        c.signal();
                    } catch (Exception e) {
                        System.out.println("2error");   
                        e.printStackTrace();   
                    } finally {
                        lock.unlock(); // 一定记得要解锁!!!
                    }
                }
            });
            t.start();
        }
        
        lock.lock();
        System.out.println("主线程拿到锁");
        System.out.println("主线程发出信号");
        // 唤醒是signal不是notify,notify是使用关键字synchronized时的唤醒方法!!!
        //  使用notify会抛java.lang.IllegalMonitorStateException异常
        c.signal();   // 唤醒,默认优先唤醒先等待的线程
//        c.signalAll();    // 唤醒全部         
        System.out.println("主线程释放锁");
        lock.unlock();
        **/
        /*---------------多个线程之间的相互唤醒------------------*/
        
        /*---------------几种等待机制------------------*/
        /**
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("1拿到锁");
                    System.out.println("1等待开始");
//                    c.await();    // 等待
//                    c.await(1, TimeUnit.SECONDS); //等待1s, 1s后解除等待开始竞争资源
//                    c.awaitNanos(1000L);// 等待1000纳秒  
//                    c.awaitUntil(new Date(new Date().getTime() + 1000)); // 等待到某一日期
                    c.awaitUninterruptibly();   // 无中断的等待,在这个等待状态下,此线程会无视对他的中断指令
                    System.out.println("1被唤醒");
                } catch (Exception e) {
                    System.out.println("1error");
                    e.printStackTrace();
                } finally {
                    lock.unlock(); 
                }
            }
        });

        t1.start();
        try {
            Thread.sleep(1000);  // 等待1s确保t1进入等待状态,避免主线程先抢到资源执行完毕后t1得到资源才开始等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();  // awaitUninterruptibly时这个语句被无视,其它等待下会抛异常java.lang.InterruptedException
        lock.lock();
        System.out.println("主线程拿到锁");
        System.out.println("主线程发出信号");
        c.signal(); // 唤醒
        lock.unlock(); // 释放锁
        **/
        /*---------------几种等待机制------------------*/
      
      

      /*-----线程3用以验证睡眠是否释放锁及验证Condition之间无法相互唤醒-----*/
      /**
        Thread t3 = new Thread(new Runnable() {
          @Override
          public void run() {
              try {
                  lock.lock();
                  System.out.println("3拿到锁");
                  System.out.println("3开始等待");
                  c.await();
                  System.out.println("3收到信号");
              } catch (Exception e) {
                  System.out.println("3error");   
                  e.printStackTrace();   
              } finally {
                  lock.unlock();
              }
          }
      });
      */
      /*-----线程3用以验证睡眠是否释放锁及验证Condition之间无法相互唤醒-----*/


      /*--------线程持有锁并睡眠时间是不会释放资源(锁)的------------*/
        /**
      lock.lock();
      t3.start();
      System.out.println("主线程拿到锁");
      System.out.println("主线程开始睡眠");
        try {
            Thread.sleep(1000); // 线程持有锁并睡眠时间是不会释放资源(锁)的
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       System.out.println("主线程睡眠结束");
       lock.unlock();
       */
       /*--------线程持有锁并睡眠时间是不会释放资源(锁)的------------*/
       
       /*---------------不同Condition之间无法相互唤醒------------------*/
        /**
      final Condition c2 = lock.newCondition();
      try {
          Thread.sleep(1000);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
        lock.lock();
//        c.signalAll();
        c2.signalAll(); // c2并不能唤醒c的等待线程
        lock.unlock();
        */
        /*---------------不同Condition之间无法相互唤醒------------------*/
    }

}

一个线程从争夺资源,到等待,再都唤醒所经历的流程如下:
1.线程调用Lock.lock()方法,现在被放入AbstractQueuedSynchronizer的争夺队列中
2.线程争夺到资源,开始执行,从AbstractQueuedSynchronizer的争夺队列中移除(确认是移除详见代码InQueueWhenDo)
3.线程开始等待,当前线程被放入Condition的等待队列中
4.其它AbstractQueuedSynchronizer的争夺队列中的线程争夺到锁并唤醒,线程从Condition的等待队列中的等待队列中移出,进入到AbstractQueuedSynchronizer的争夺队列中等待资源被释放后开始争夺资源。
(优先唤醒先等待的线程并不代表被唤醒的线程马上可以获得资源,只是将其放到竞争队列中去了。见AbstractQueuedSynchronizer中的enq(Node)方法,signal方法会调用到他)
5.线程重新争夺到资源,从AbstractQueuedSynchronizer的争夺队列中移除,继续执行。
6.执行完毕,释放资源。

代码InQueueWhenDo:

package ReenTrantLock;

import java.util.concurrent.locks.ReentrantLock;

public class InQueueWhenDo {
 
    public static void main(String[] args) {
        final ReentrantLock lock = new ReentrantLock();

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("3开始争夺");
                    lock.lock();
                    System.out.println("3拿到锁");
                    System.out.println("3观察当前争夺队列长度" + lock.getQueueLength());
                } catch (Exception e) {
                    System.out.println("3error");   
                    e.printStackTrace();   
                } finally {
                    lock.unlock();
                }
            }
        });
        
        System.out.println("主线程争夺锁");
        lock.lock();
        System.out.println("主线程拿到锁");
        t3.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("主线程观察当前争夺队列长度" + lock.getQueueLength());
        lock.unlock();
        System.out.println("主线程释放锁");
    }
}

运行结果:可以看到运行中的线程已经从竞争队列中移出

主线程争夺锁
主线程拿到锁
3开始争夺
主线程观察当前争夺队列长度1
主线程释放锁
3拿到锁
3观察当前争夺队列长度0


ReentrantLock的几个其它方法(见代码OtherMethodOfRTL):
lock.getHoldCount());// 持有当前锁的线程数
lock.getQueueLength()); // 当前竞争此锁队列长度
lock.getWaitQueueLength(c));// 当前锁等待队列长度
lock.isFair());// 是否是公平锁
lock.isLocked());// 当前锁是否有线程持有
lock.isHeldByCurrentThread());// 当前锁是否被当前线程持有
lock.hasWaiters(c));// 当前condition是否有线程在等待
lock.hasQueuedThread(t1));// 目标线程是否在这个锁的竞争队列中
lock.hasQueuedThreads());// 是否有线程在当前锁的竞争队列中

代码OtherMethodOfRTL:

package ReenTrantLock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class OtherMethodOfRTL {

    public static void main(String[] args) {
        final ReentrantLock lock = new ReentrantLock();
        final Condition c = lock.newCondition();

        final Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("T1得到锁");
                    System.out.println("T1开始等待");
                    c.await();
                    System.out.println("T1收到信号");
                    System.out.println("T1发出信号");
                    c.signal();
                } catch (InterruptedException e) {
                    System.out.println("1error");
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });
        for (int i = 0; i < 5; i++) {
            final int j = i+10;
            final Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        lock.lock();
                        System.out.println(j + "拿到锁");
                        System.out.println(j + "等待信号");
                        System.out.println("持有当前锁的线程数:" + lock.getHoldCount());
                        System.out.println("当前竞争此锁队列长度:" + lock.getQueueLength());
                        System.out.println("当前锁等待队列长度:" + lock.getWaitQueueLength(c));
                        System.out.println("是否是公平锁:" + lock.isFair());
                        System.out.println("当前锁是否有线程持有:" + lock.isLocked());
                        System.out.println("当前锁是否被当前线程持有:" + lock.isHeldByCurrentThread());
                        System.out.println("当前condition是否有线程在等待:" + lock.hasWaiters(c));
                        System.out.println("目标线程是否在这个锁的竞争队列中:" + lock.hasQueuedThread(t1));
                        System.out.println("是否有线程在当前锁的竞争队列中:" + lock.hasQueuedThreads());
                        c.await(); 
                        System.out.println(j + "收到信号");
                        System.out.println(j + "发出信号");
                        System.out.println("当前condition是否有线程在等待:" + lock.hasWaiters(c));
                        System.out.println("目标线程是否在这个锁的竞争队列中:" + lock.hasQueuedThread(t1));
                        System.out.println("是否有线程在当前锁的竞争队列中:" + lock.hasQueuedThreads());
                        c.signal();
                    } catch (Exception e) {
                        System.out.println("2error");   
                        e.printStackTrace();   
                    } finally {
                        lock.unlock(); // 一定记得要解锁!!!
                    }
                }
            });
            t.start();
        }
        
        t1.start();
        lock.lock();
        System.out.println("主线程拿到锁");
        System.out.println("主线程发出信号");
        c.signal();   // 唤醒,默认优先唤醒先等待的线程
        System.out.println("主线程释放锁");
        lock.unlock();
    }

}

 

参考:知乎:Java中Lock,tryLock,lockInterruptibly有什么区别?

           怎么理解Condition

           ReentrantLock解析

           深入理解多线程编程

           Condition使用总结

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yue_hu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值