多线程(五)线程同步(中)-Lock,Condition, ReadWriteLock

时间:2022-01-21 17:58:36

  线程同步(上)提到了七种同步方式,讲述了四种,剩下三种,大家可以继续深入了解下:
上篇博文地址:多线程(四) 线程同步(上)

<五>Lock 接口

Lock是java.util.concurrent.locks包下的接口,源码如下,注意相关接口:

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

*
* @see ReentrantLock
* @see Condition
* @see ReadWriteLock
*
* @since 1.5
* @author Doug Lea
*/
public interface Lock {

void lock();

void lockInterruptibly() throws InterruptedException;

boolean tryLock();

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

void unlock();

Condition newCondition();
}

需要注意的类:
ReentrantLock:
public class ReentrantLock implements Lock, java.io.Serializable {}

Condition :接口
public interface Condition{}

ReadWriteLock 接口
public interface ReadWriteLock{}

ReentrantReadWriteLock:
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable{}

Lock的特性:

  1).Lock不是Java语言内置的;

  2).synchronized是在JVM层面上实现的,如果代码执行出现异常,JVM会自动释放锁,但是Lock不行,要保证锁一定会被释放,就必须将unLock放到finally{}中(手动释放);

  3).在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetarntLock,但是在很激烈的情况下,synchronized的性能会下降几十倍;

  4).ReentrantLock增加了锁:

    a. void lock(); // 无条件的锁;

    b. void lockInterruptibly throws InterruptedException;//可中断的锁;

解释:使用ReentrantLock如果获取了锁立即返回,如果没有获取锁,当前线程处于休眠状态,直到获得锁或者当前线程可以被别的线程中断去做其他的事情;但是如果是synchronized的话,如果没有获取到锁,则会一直等待下去;

    c. boolean tryLock();//如果获取了锁立即返回true,如果别的线程正持有,立即返回false,不会等待;

    d. boolean tryLock(long timeout,TimeUnit unit);//如果获取了锁立即返回true,如果别的线程正持有锁,会等待参数给的时间,在等待的过程中,如果获取锁,则返回true,如果等待超时,返回false;

ReentrantLock及ReadWriteLock的使用方式,推荐大家看这篇博文:锁对象Lock-同步问题更完美的处理方式

  在这里要介绍的是Condition:

Condition的特性:

    1.Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的这些方法是和同步锁捆绑使用的;而Condition是需要与互斥锁/共享锁捆绑使用的。

    2.Condition它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。
    例如,假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒”读线程”;当从缓冲区读出数据之后,唤醒”写线程”;并且当缓冲区满的时候,”写线程”需要等待;当缓冲区为空时,”读线程”需要等待。

     如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒”读线程”时,不可能通过notify()或notifyAll()明确的指定唤醒”读线程”,而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。

示例代码:

package com.blog.spring.thread;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionTask {

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 {
System.out.println("==============当前添加线程:"+Thread.currentThread().getName()+"================");
while(lists.size() == 3) {//当集合已满,则"添加"线程等待
System.out.println("当前集合大小为: " + lists.size()+"-->集合已满,添加线程"+Thread.currentThread().getName()+"开始等待,去唤醒减少线程");
this.subCondition.signal();
addCondition.await();
}

num++;
String str="元素" + num;
lists.add(str);
System.out.println("添加 [" + str + "]");
System.out.println("当前集合大小为: " + lists.size());
System.out.println("当前添加线程为: " + Thread.currentThread().getName());
System.out.println("==============去唤醒减少线程================");
this.subCondition.signal();

} catch (InterruptedException e) {
e.printStackTrace();
} finally {//释放锁
lock.unlock();
}
}


public void sub() {
lock.lock();

try {
System.out.println("==============当前减少线程:"+Thread.currentThread().getName()+"================");
while(lists.size() == 0) {//当集合为空时,"减少"线程等待
System.out.println("当前集合大小为: " + lists.size()+"-->集合为空,减少线程"+Thread.currentThread().getName()+"开始等待,去唤醒添加线程");
addCondition.signal();
subCondition.await();
}

num--;
String str = lists.get(0);
lists.remove(0);
System.out.println("移除 [" + str + "]");
System.out.println("当前集合大小为: " + lists.size());
System.out.println("当前减少线程为: " + Thread.currentThread().getName());
System.out.println("===============去唤醒添加线程===============");
addCondition.signal();

} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}


}



//测试类
package com.blog.spring.thread;

public class TestCondition {

public static void main(String[] args) {

final ConditionTask task = new ConditionTask();

final ConditionTask task = new ConditionTask();

for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
task.add();
}
},"添加线程"+i).start();
}

for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
task.sub();
}
},"减少线程"+i).start();
}
}

运行结果:

==============当前添加线程:添加线程0================
添加 [元素1]
当前集合大小为: 1
当前添加线程为: 添加线程0
==============去唤醒减少线程================
==============当前添加线程:添加线程6================
添加 [元素2]
当前集合大小为: 2
当前添加线程为: 添加线程6
==============去唤醒减少线程================
==============当前添加线程:添加线程9================
添加 [元素3]
当前集合大小为: 3
当前添加线程为: 添加线程9
==============去唤醒减少线程================
==============当前添加线程:添加线程1================
当前集合大小为: 3-->集合已满,添加线程添加线程1开始等待,去唤醒减少线程
==============当前添加线程:添加线程2================
当前集合大小为: 3-->集合已满,添加线程添加线程2开始等待,去唤醒减少线程
==============当前添加线程:添加线程3================
当前集合大小为: 3-->集合已满,添加线程添加线程3开始等待,去唤醒减少线程
==============当前添加线程:添加线程4================
当前集合大小为: 3-->集合已满,添加线程添加线程4开始等待,去唤醒减少线程
==============当前添加线程:添加线程5================
当前集合大小为: 3-->集合已满,添加线程添加线程5开始等待,去唤醒减少线程
==============当前添加线程:添加线程7================
当前集合大小为: 3-->集合已满,添加线程添加线程7开始等待,去唤醒减少线程
==============当前减少线程:减少线程3================
移除 [元素1]
当前集合大小为: 2
当前减少线程为: 减少线程3
===============去唤醒添加线程===============
==============当前添加线程:添加线程8================
添加 [元素3]
当前集合大小为: 3
当前添加线程为: 添加线程8
==============去唤醒减少线程================
==============当前减少线程:减少线程0================
移除 [元素2]
当前集合大小为: 2
当前减少线程为: 减少线程0
===============去唤醒添加线程===============
==============当前减少线程:减少线程1================
移除 [元素3]
当前集合大小为: 1
当前减少线程为: 减少线程1
===============去唤醒添加线程===============
==============当前减少线程:减少线程2================
移除 [元素3]
当前集合大小为: 0
当前减少线程为: 减少线程2
===============去唤醒添加线程===============
==============当前减少线程:减少线程4================
当前集合大小为: 0-->集合为空,减少线程减少线程4开始等待,去唤醒添加线程
添加 [元素1]
当前集合大小为: 1
当前添加线程为: 添加线程1
==============去唤醒减少线程================
==============当前减少线程:减少线程5================
移除 [元素1]
当前集合大小为: 0
当前减少线程为: 减少线程5
===============去唤醒添加线程===============
==============当前减少线程:减少线程6================
当前集合大小为: 0-->集合为空,减少线程减少线程6开始等待,去唤醒添加线程
==============当前减少线程:减少线程7================
当前集合大小为: 0-->集合为空,减少线程减少线程7开始等待,去唤醒添加线程
==============当前减少线程:减少线程8================
当前集合大小为: 0-->集合为空,减少线程减少线程8开始等待,去唤醒添加线程
==============当前减少线程:减少线程9================
当前集合大小为: 0-->集合为空,减少线程减少线程9开始等待,去唤醒添加线程
添加 [元素1]
当前集合大小为: 1
当前添加线程为: 添加线程2
==============去唤醒减少线程================
添加 [元素2]
当前集合大小为: 2
当前添加线程为: 添加线程3
==============去唤醒减少线程================
添加 [元素3]
当前集合大小为: 3
当前添加线程为: 添加线程4
==============去唤醒减少线程================
当前集合大小为: 3-->集合已满,添加线程添加线程5开始等待,去唤醒减少线程
移除 [元素1]
当前集合大小为: 2
当前减少线程为: 减少线程4
===============去唤醒添加线程===============
添加 [元素3]
当前集合大小为: 3
当前添加线程为: 添加线程7
==============去唤醒减少线程================
移除 [元素2]
当前集合大小为: 2
当前减少线程为: 减少线程6
===============去唤醒添加线程===============
移除 [元素3]
当前集合大小为: 1
当前减少线程为: 减少线程7
===============去唤醒添加线程===============
移除 [元素3]
当前集合大小为: 0
当前减少线程为: 减少线程8
===============去唤醒添加线程===============
当前集合大小为: 0-->集合为空,减少线程减少线程9开始等待,去唤醒添加线程
添加 [元素1]
当前集合大小为: 1
当前添加线程为: 添加线程5
==============去唤醒减少线程================
移除 [元素1]
当前集合大小为: 0
当前减少线程为: 减少线程9
===============去唤醒添加线程===============

读写锁很重要,特在此保留一份:

package com.blog.spring.thread;

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* Created by hxf on 17/3/29.
*/

public class ReadWriteLockTest {

public static void main(String[] args) {
final Data data = new Data();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 5; j++) {
data.set(new Random().nextInt(30));
}
}
}).start();
}
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 5; j++) {
data.get();
}
}
}).start();
}
}

static class Data {
private int data;// 共享数据
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public void set(int data) {
rwl.writeLock().lock();// 取到写锁
try {
System.out.println(Thread.currentThread().getName() + "准备写入数据");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + "写入" + this.data);
} finally {
rwl.writeLock().unlock();// 释放写锁
}
}

public void get() {
rwl.readLock().lock();// 取到读锁
try {
System.out.println(Thread.currentThread().getName() + "准备读取数据");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "读取" + this.data);
} finally {
rwl.readLock().unlock();// 释放读锁
}
}
}
}

运行结果:

Thread-0准备写入数据
Thread-0写入9
Thread-0准备写入数据
Thread-0写入15
Thread-0准备写入数据
Thread-0写入14
Thread-1准备写入数据
Thread-1写入17
Thread-1准备写入数据
Thread-1写入27
Thread-1准备写入数据
Thread-1写入26
Thread-1准备写入数据
Thread-1写入22
Thread-1准备写入数据
Thread-1写入19
Thread-2准备写入数据
Thread-2写入18
Thread-2准备写入数据
Thread-2写入6
Thread-2准备写入数据
Thread-2写入16
Thread-2准备写入数据
Thread-2写入18
Thread-2准备写入数据
Thread-2写入29
Thread-3准备读取数据
Thread-4准备读取数据
Thread-5准备读取数据
Thread-5读取29
Thread-4读取29
Thread-3读取29
Thread-0准备写入数据
Thread-0写入24
Thread-0准备写入数据
Thread-0写入16
Thread-5准备读取数据
Thread-4准备读取数据
Thread-3准备读取数据
Thread-3读取16
Thread-4读取16
Thread-5读取16
Thread-4准备读取数据
Thread-3准备读取数据
Thread-5准备读取数据
Thread-4读取16
Thread-5读取16
Thread-3读取16
Thread-5准备读取数据
Thread-4准备读取数据
Thread-3准备读取数据
Thread-5读取16
Thread-3读取16
Thread-4读取16
Thread-3准备读取数据
Thread-5准备读取数据
Thread-4准备读取数据
Thread-5读取16
Thread-3读取16
Thread-4读取16
Process finished with exit code 0

思考:比较Condition与ReadWriteLock(ReentrantReadWriteLock是实现类)运行结果的区别?原因是啥?

Condition中的锁为:
private final Lock lock = new ReentrantLock();

结果显示:写入和写入互斥,读取和写入互斥,读取和读取互斥
不能并发执行,效率较低。Condition能够精确唤醒某种条件的线程(注意观察上面运行结果,如果拥有这种条件的线程数就一个,那么就能精确到线程,否则就是其中一个线程(随机唤醒)。),但不能并发执行-即所有线程之间都是互斥的。
  那么在这思考下Condition与Object(wait/notify,notifyAll)有什么区别?

ReadWriteLock中的锁为:
private ReadWriteLock rwl = new ReentrantReadWriteLock();

结果显示:虽然写入和写入互斥了,读取和写入也互斥了,但是读取和读取之间不互斥了,能够并发执行读取数据,相对效率较高,我想现在应该能很好的理解下面这句话了:

  在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetarntLock,但是在很激烈的情况下,synchronized的性能会下降几十倍;

参考文章:(http://www.cnblogs.com/Wanted-Tao/p/6378942.html);
http://blog.csdn.net/ghsau/article/details/7461369

上篇文章:多线程(四) 线程同步(上)-synchronized,volatile