Java 多线程基础——Lock 类
Lock 类是 Java 类来提供的功能,丰富的 api 使得 Lock 类的同步功能比 synchronized 的同步更强大。本文对此进行详细介绍,下面跟着小编一起来看下吧
之前已经说道,JVM 提供了 synchronized 关键字来实现对变量的同步访问以及用 wait 和 notify 来实现线程间通信。在 jdk1.5 以后,JAVA 提供了 Lock 类来实现和 synchronized 一样的功能,并且还提供了 Condition 来显示线程间通信。
Lock 类是 Java 类来提供的功能,丰富的 api 使得 Lock 类的同步功能比 synchronized 的同步更强大。本文章的所有代码均在 Lock 类例子的代码
本文主要介绍一下内容:
1.Lock 类
2.Lock 类其他功能
3.Condition 类
4.Condition 类其他功能
5. 读写锁
Lock 类
Lock 类实际上是一个接口,我们在实例化的时候实际上是实例化实现了该接口的类 Lock lock = new ReentrantLock();。用 synchronized 的时候,synchronized 可以修饰方法,或者对一段代码块进行同步处理。
前面讲过,针对需要同步处理的代码设置对象监视器,比整个方法用 synchronized 修饰要好。Lock 类的用法也是这样,通过 Lock 对象 lock,用 lock.lock 来加锁,用 lock.unlock 来释放锁。在两者中间放置需要同步处理的代码。
具体的例子如下:
- public class MyConditionService {
- private Lock lock = new ReentrantLock();
- public void testMethod() {
- lock.lock();
- for (int i = 0; i < 5; i++) {
- System.out.println("ThreadName = " + Thread.currentThread().getName() + (" " + (i + 1)));
- }
- lock.unlock();
- }
- }
测试的代码如下:
- MyConditionService service = new MyConditionService();
- new Thread(service: :testMethod).start();
- new Thread(service: :testMethod).start();
- new Thread(service: :testMethod).start();
- new Thread(service: :testMethod).start();
- new Thread(service: :testMethod).start();
- Thread.sleep(1000 * 5);
结果太长就不放出来,具体可以看我源码。总之,就是每个线程的打印 1-5 都是同步进行,顺序没有乱。
通过下面的例子,可以看出 Lock 对象加锁的时候也是一个对象锁,持续对象监视器的线程才能执行同步代码,其他线程只能等待该线程释放对象监视器。
- public class MyConditionMoreService {
- private Lock lock = new ReentrantLock();
- public void methodA() {
- try {
- lock.lock();
- System.out.println("methodA begin ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
- Thread.sleep(1000 * 5);
- System.out.println("methodA end ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
- } catch(Exception e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- }
- public void methodB() {
- try {
- lock.lock();
- System.out.println("methodB begin ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
- Thread.sleep(1000 * 5);
- System.out.println("methodB end ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
- } catch(Exception e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- }
- }
测试代码如下:
- public void testMethod() throws Exception {
- MyConditionMoreService service = new MyConditionMoreService();
- ThreadA a = new ThreadA(service);
- a.setName("A");
- a.start();
- ThreadA aa = new ThreadA(service);
- aa.setName("AA");
- aa.start();
- ThreadB b = new ThreadB(service);
- b.setName("B");
- b.start();
- ThreadB bb = new ThreadB(service);
- bb.setName("BB");
- bb.start();
- Thread.sleep(1000 * 30);
- }
- public class ThreadA extends Thread {
- private MyConditionMoreService service;
- public ThreadA(MyConditionMoreService service) {
- this.service = service;
- }@Override public void run() {
- service.methodA();
- }
- }
- public class ThreadB extends Thread {
- private MyConditionMoreService service;
- public ThreadB(MyConditionMoreService service) {
- this.service = service;
- }@Override public void run() {
- super.run();
- service.methodB();
- }
- }
结果如下:
- methodA begin ThreadName=A time=1485590913520methodA end ThreadName=A time=1485590918522methodA begin ThreadName=AA time=1485590918522methodA end ThreadName=AA time=1485590923525methodB begin ThreadName=B time=1485590923525methodB end ThreadName=B time=1485590928528methodB begin ThreadName=BB time=1485590928529methodB end ThreadName=BB time=1485590933533
可以看出 Lock 类加锁确实是对象锁。针对同一个 lock 对象执行的 lock.lock 是获得对象监视器的线程才能执行同步代码 其他线程都要等待。
在这个例子中,加锁,和释放锁都是在 try-finally。这样的好处是在任何异常发生的情况下,
都能保障锁的释放。
Lock 类其他的功能
如果 Lock 类只有 lock 和 unlock 方法也太简单了,Lock 类提供了丰富的加锁的方法和对加锁的情况判断。主要有
1. 实现锁的公平
2. 获取当前线程调用 lock 的次数,也就是获取当前线程锁定的个数
3. 获取等待锁的线程数
4. 查询指定的线程是否等待获取此锁定
5. 查询是否有线程等待获取此锁定
6. 查询当前线程是否持有锁定
7. 判断一个锁是不是被线程持有
8. 加锁时如果中断则不加锁,进入异常处理
9. 尝试加锁,如果该锁未被其他线程持有的情况下成功
实现公平锁
在实例化锁对象的时候,构造方法有 2 个,一个是无参构造方法,一个是传入一个 boolean 变量的构造方法。当传入值为 true 的时候,该锁为公平锁。默认不传参数是非公平锁。
公平锁:按照线程加锁的顺序来获取锁
非公平锁:随机竞争来得到锁
此外,JAVA 还提供 isFair() 来判断一个锁是不是公平锁。
获取当前线程锁定的个数
Java 提供了 getHoldCount() 方法来获取当前线程的锁定个数。所谓锁定个数就是当前线程调用 lock 方法的次数。一般一个方法只会调用一个 lock 方法,但是有可能在同步代码中还有调用了别的方法,那个方法内部有同步代码。这样,getHoldCount() 返回值就是大于 1。
下面的方法用来判断等待锁的情况
获取等待锁的线程数
Java 提供了 getQueueLength() 方法来得到等待锁释放的线程的个数。
查询指定的线程是否等待获取此锁定
Java 提供了 hasQueuedThread(Thread thread) 查询该 Thread 是否等待该 lock 对象的释放。
查询是否有线程等待获取此锁定
同样,Java 提供了一个简单判断是否有线程在等待锁释放即 hasQueuedThreads()。
下面的方法用来判断持有锁的情况
查询当前线程是否持有锁定
Java 不仅提供了判断是否有线程在等待锁释放的方法,还提供了是否当前线程持有锁即 isHeldByCurrentThread(),判断当前线程是否有此锁定。
判断一个锁是不是被线程持有
同样,Java 提供了简单判断一个锁是不是被一个线程持有,即 isLocked()
下面的方法用来实现多种方式加锁
加锁时如果中断则不加锁,进入异常处理
Lock 类提供了多种选择的加锁方法,lockInterruptibly() 也可以实现加锁,但是当线程被中断的时候,就会加锁失败,进行异常处理阶段。一般这种情况出现在该线程已经被打上 interrupted 的标记了。
尝试加锁,如果该锁未被其他线程持有的情况下成功
Java 提供了 tryLock() 方法来进行尝试加锁,只有该锁未被其他线程持有的基础上,才会成功加锁。
上面介绍了 Lock 类来实现代码的同步处理,下面介绍 Condition 类来实现 wait/notify 机制。
Condition 类
Condition 是 Java 提供了来实现等待 / 通知的类,Condition 类还提供比 wait/notify 更丰富的功能,Condition 对象是由 lock 对象所创建的。但是同一个锁可以创建多个 Condition 的对象,即创建多个对象监视器。这样的好处就是可以指定唤醒线程。notify 唤醒的线程是随机唤醒一个。
下面,看一个例子,显示简单的等待 / 通知
- public class ConditionWaitNotifyService {
- private Lock lock = new ReentrantLock();
- public Condition condition = lock.newCondition();
- public void await() {
- try {
- lock.lock();
- System.out.println("await的时间为 " + System.currentTimeMillis());
- condition.await();
- System.out.println("await结束的时间" + System.currentTimeMillis());
- } catch(Exception e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- }
- public void signal() {
- try {
- lock.lock();
- System.out.println("sign的时间为" + System.currentTimeMillis());
- condition.signal();
- } finally {
- lock.unlock();
- }
- }
- }
测试的代码如下:
- ConditionWaitNotifyService service = new ConditionWaitNotifyService();
- new Thread(service: :await).start();
- Thread.sleep(1000 * 3);
- service.signal();
- Thread.sleep(1000);
结果如下:
await 的时间为 1485610107421sign 的时间为 1485610110423await 结束的时间 1485610110423
condition 对象通过 lock.newCondition() 来创建,用 condition.await() 来实现让线程等待,是线程进入阻塞。用 condition.jiujiu360.comsignal() 来实现唤醒线程。唤醒的线程是用同一个 conditon 对象调用 await() 方法而进入阻塞。并且和 wait/notify 一样,await() 和 signal() 也是在同步代码区内执行。
此外看出 await 结束的语句是在获取通知之后才执行,确实实现了 wait/notify 的功能。下面这个例子是展示唤醒制定的线程。
- ConditionAllService service = new ConditionAllService();
- Thread a = new Thread(service: :awaitA);
- a.setName("A");
- a.start();
- Thread b = new Thread(service: :awaitB);
- b.setName("B");
- b.start();
- Thread.sleep(1000 * 3);
- service.signAAll();
- Thread.sleep(1000 * 4);
结果如下:
begin awaitA 时间为 1485611065974ThreadName=Abegin awaitB 时间为 1485611065975ThreadName=BsignAll 的时间为 1485611068979ThreadName=mainend awaitA 时间为 1485611068979ThreadName=A
该结果确实展示用同一个 condition 对象来实现等待通知。
对于等待 / 通知机制,简化而言,就是等待一个条件,当条件不满足时,就进入等待,等条件满足时,就通知等待的线程开始执行。为了实现这种功能,需要进行 wait 的代码部分与需要进行通知的代码部分必须放在同一个对象监视器里面。执行才能实现多个阻塞的线程同步执行代码,等待与通知的线程也是同步进行。对于 wait/notify 而言,对象监视器与等待条件结合在一起 即 synchronized(对象) 利用该对象去调用 wait 以及 notify。但是对于 Condition 类,是对象监视器与条件分开,Lock 类来实现对象监视器,condition 对象来负责条件,去调用 await 以及 signal。
Condition 类的其他功能
和 wait 类提供了一个最长等待时间,awaitUntil(Date deadline) 在到达指定时间之后,线程
会自动唤醒。但是无论是 await 或者 awaitUntil,当线程中断时,进行阻塞的线程会产生中断异常。Java 提供了一个 awaitUninterruptibly 的方法,使即使线程中断时,进行阻塞的线程也不会产生中断异常。
读写锁
Lock 类除了提供了 ReentrantLock 的锁以外,还提供了 ReentrantReadWriteLock 的锁。读写锁分成两个锁,一个锁是读锁,一个锁是写锁。读锁与读锁之间是共享的,读锁与写锁之间是互斥的,写锁与写锁之间也是互斥的。
看下面的读读共享的例子:
- public class ReadReadService {
- private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
- public void read() {
- try {
- try {
- lock.readLock().lock();
- System.out.println("获得读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
- Thread.sleep(1000 * 10);
- } finally {
- lock.readLock().unlock();
- }
- } catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
测试的代码和结果如下:
- ReadReadService service = new ReadReadService();
- Thread a = new Thread(service: :read);
- a.setName("A");
- Thread b = new Thread(service: :read);
- b.setName("B");
- a.start();
- b.start();
结果如下:
获得读锁 A 1485614976979 获得读锁 B 1485614976981
两个线程几乎同时执行同步代码。
下面的例子是写写互斥的例子
- public class WriteWriteService {
- private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
- public void write() {
- try {
- try {
- lock.writeLock().lock();
- System.out.println("获得写锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
- Thread.sleep(1000 * 10);
- } finally {
- lock.writeLock().unlock();
- }
- } catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
测试代码和结果如下:
- WriteWriteService service = new WriteWriteService();
- Thread a = new Thread(service: :write);
- a.setName("A");
- Thread b = newThread(service: :write);
- b.setName("B");
- a.start();
- b.start();
- Thread.sleep(1000 * 30);
结果如下:
获得写锁 A 1485615316519 获得写锁 B 1485615326524
两个线程同步执行代码
读写互斥的例子:
- public class WriteReadService {
- private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
- public void read() {
- try {
- try {
- lock.readLock().lock();
- System.out.println("获得读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
- Thread.sleep(1000 * 10);
- } finally {
- lock.readLock().unlock();
- }
- } catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- public void write() {
- try {
- try {
- lock.writeLock().lock();
- System.out.println("获得写锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
- Thread.sleep(1000 * 10);
- } finally {
- lock.writeLock().unlock();
- }
- } catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
测试的代码如下:
- WriteReadService service = new WriteReadService();
- ThreadThread(service: :write);
- a.setName("A");
- a.start();
- Thread.sleep(1000);
- ThreadThread(service: :read);
- b.setName("B");
- b.start();
- Thread.sleep(1000 * 30);
结果如下:
获得写锁 A 1485615633790 获得读锁 B 1485615643792
两个线程读写之间也是同步执行代码。
就爱阅读 www.92to.com 网友整理上传, 为您提供最全的知识大全, 期待您的分享,转载请注明出处。
来源: http://www.92to.com/bangong/2017/02-18/17276880.html