`
yidongkaifa
  • 浏览: 4048574 次
文章分类
社区版块
存档分类
最新评论

Java5 多线程(三)--Lock和Condition实现线程同步通信

 
阅读更多

1>Lock:
Lock比传统线程模型中的Synchronied方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码段要实现同步互斥的效果,它们必须用同一个Lock对象,锁是在代表要操作的资源的类的内部方法中,而不是线程代码中.
上面的输出器Outputer类就可以这样改写:
class Outputer2 {
// 声明一个锁
Lock lock = new ReentrantLock();
public void print(String name) {
//把要互斥的代码写在lock()和unlock()方法之间
lock.lock();
try {
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
System.out.println();// 打印完字符串换行
} finally{
//如果中途抛出异常,那么这把锁就没有被解锁,别人就进不来了
//所以写在finally里面
lock.unlock();
}
}
}
读写锁,分为读锁和写锁,多个读锁不互斥,读锁和写锁互斥,写锁与写锁互斥,这是JVM自己控制的,你只要上好相应的锁即可,如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁.总之,读的时候上读锁,写的时候上写锁!
看如下程序: 新建6个线程,3个线程用来读,3个线程用来写,
final Queue3 q3 = new Queue3();
for (int i = 0; i < 3; i++) {
new Thread() {
public void run() {
while (true) {
q3.get();
}
}
}.start();
new Thread() {
public void run() {
while (true) {
q3.put(new Random().nextInt(10000));
}
}
}.start();
}
然后在编写一个类Queue3 里面有一个读方法和写方法:
class Queue3 {
private Object data = null;// 共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
//读写锁
ReadWriteLock rwl = new ReentrantReadWriteLock();
// 相当于读操作
public void get() {
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+ " be ready to read data!");
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName()
+ "have read data :" + data);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwl.readLock().unlock();
}
}
// 相当于写操作
public void put(Object data) {
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+ " be ready to write data!");
Thread.sleep((long) (Math.random() * 1000));
this.data = data;
System.out.println(Thread.currentThread().getName()
+ " have write data: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwl.writeLock().unlock();
}
}
}
这样可以实现正常的逻辑,如果我们把读写锁相关的代码注释,发现程序正准备写的时候,就有线程读了,发现准备读的时候,有线程去写,这样不符合我们的逻辑
通过Java5的新特新可以很轻松的解决这样的问题.
查看Java API ReentrantReadWriteLock 上面有经典(缓存)的用法.这是上面的伪代码.
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.

if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
基于上面的例子,我们可以实现一个缓存系统.
Map<String, Object> cache = new HashMap<String, Object>();
ReadWriteLock rrwl = new ReentrantReadWriteLock();
public Object getData(String key) {
rrwl.readLock().lock();
Object value = null;
try {
value = cache.get(key);
if (value == null) {
rrwl.readLock().unlock();
rrwl.writeLock().lock();
try {
//假设三个线程同时去获取写锁,我们知道只有第一个线程能够获取
//那么其他两个线程只有等了,如果第一个线程按流程执行完后,刚才的两个线程可以得到写锁了,
//然后接着就可以修改数据了(赋值).所以加上判断!
if (value == null){//为什么还要在这里判断一次.?
value = "hello world";
}
// 降级,通过释放写锁之前获取读锁
rrwl.readLock().lock();
} finally {
rrwl.writeLock().unlock();
}
}
} finally {
rrwl.readLock().unlock();
}
return value;
}

2>Condition:
Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能,在等待Condition时,允许发生"虚假唤醒",这通常作为对基础平台语义的让步,对于大多数应用程序,这带来的实际影响很小,因为Condition应该总是在一个循环中被等待,并测试正被等待的状态声明.某个实现可以随意移除可能的虚假唤醒,但是建议程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待.
一个锁内部可以有多个Condition,即有多路等待和通知,可以参看jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例,从中要体味算法,还要体味面向对象的封装.在传统的线程机制中,一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象.(如果只用一个Condition,两个放的都在等,一旦一个放进去了,那么它会通知可能会导致另一个放的接着往下走).
我们也可以通过Lock和Condition来实现上面我们讲的例子:
子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。
这里只实现其中的一个方法,因为其他的是一样的.
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 默认子线程先执行
boolean isShouldSub = true;
public void sub(int k) {
lock.lock();//相当于synchronied
try {
while(!isShouldSub) {
try {
condition.await();//然后实现通信
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i <= 10; i++) {
System.out
.println("sub thread sequence " + i + " loop of " + k);
}
// 子线程做完了,把它置为false
isShouldSub = false;
// 并且唤醒主线程
condition.signal();
} finally {
lock.unlock();
}
}
可以使用Lock和Condition来实现一个缓冲队列(要区别缓冲和缓存的区别),其实jdk api有这样的例子,如下:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull =lock.newCondition();
final Condition notEmpty =lock.newCondition();

final Object[] items = new Object[100];
int putptr, takeptr, count;

public void put(Object x) throws InterruptedException {
lock.lock();//第一步实现互斥
try {
while (count == items.length)//如果没有往数组放,线程阻塞
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;//如果putptr已经是数组的最后一个,那么putptr置为0,从第一个开始放
++count;//放完后,把总数加一
notEmpty.signal();//通知其他线程可以取了
} finally {
lock.unlock();
}

}

public Object take() throws InterruptedException {
lock.lock();
try {

while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}

}
}
这个逻辑比较好理解,但是我们可以看到上面的程序用了两个Condition呢?用一个Condition似乎也能实现.
public void put(Object x) throws InterruptedException {
//如果有5个线程执行到此方法里面,那么只有一个线程获取到锁
lock.lock();// 锁住了别的线程就不能进来了,包括下面的take()因为他们用的是同一把锁
try {
//如果已经放满
while (count == items.length)
notFull.await();//执行到此,锁就释放了,可能这里就有5个线程在此等,其他线程就可以调用take()方法去取了然后调用signal()然而5个线程中,只有一个线程能被唤醒.该被唤醒的线程执行到signal时候,唤醒其他线程.如果用一个Condition,唤醒的可能就是上面的4个线程,而这个4个线程是往里面(put),而应该唤醒的是去取(take())线程.因为已经放满了如果再通知线程去放,那么就出现逻辑错误了.所以这里用到两个Condition的妙处!
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}

}
据此,我们可以改变
子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。的例子,这个例子是两个线程之间的跳转,那么如果实现三个线程之间的轮循,比如:线程1循环10,线程2循环100,线程3循环20次,然后又是线程1,接着线程2...一直轮循50次.
class Business3 {
Lock lock = new ReentrantLock();
Condition sub1 = lock.newCondition();
Condition sub2 = lock.newCondition();
Condition sub3 = lock.newCondition();
//默认线程1执行
int shouldSub = 1;
public void sub1(int k) {
lock.lock();//相当于synchronied
try {
while (shouldSub!=1) {
try {
sub1.await();//然后实现通信
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i <= 10; i++) {
System.out
.println("sub1 thread sequence " + i + " loop of " + k);
}
// 把值置为2,然线程2可执行
shouldSub = 2;
// 线程1做完后,只唤醒线程2
sub2.signal();
} finally {
lock.unlock();
}
}
public void sub2(int k) {
lock.lock();
try {
while (shouldSub!=2) {
try {
sub2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i <= 100; i++) {
System.out.println("sub2 thread sequence " + i + " loop of "
+ k);
}
// 把值置为3,然线程3可执行
shouldSub = 3;
// 线程2做完后,只唤醒线程3
sub3.signal();
} finally {
lock.unlock();
}
}
public void sub3(int k) {
lock.lock();
try {
while (shouldSub!=3) {
try {
sub3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i <= 20; i++) {
System.out.println("sub3 thread sequence " + i + " loop of "
+ k);
}
// 把值置为1,然线程1可执行
shouldSub = 1;
// 线程3做完后,只唤醒线程1
sub1.signal();
} finally {
lock.unlock();
}
}

转载请注明出处:http://blog.csdn.net/johnny901114/article/details/8695708

分享到:
评论

相关推荐

    java多线程实现生产者和消费者

    java多线程实现生产者和消费者 ,4种实现方式,分别为synchronizated,condition和lock,信号量,阻塞队列

    56.Lock-ReentranLock-使用多个Condition实现通知部分线程.mp4

    在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。

    Java学习源码Java多线程的代码

    在char01包里放置Java多线程基本知识的代码。内容如下: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 在char02包里放置了Java对变量和对象并发访问的知识的代码...

    基础技术部牛路《Java多线程入阶分享》纯干货

    Java多线程入阶干货分享 1.使用线程的经验:设置名称、响应中断、使用ThreadLocal 2.Executor:ExecutorService和Future 3.阻塞队列:put和take、offer和poll、drainTo 4.线程间通信:lock、condition、wait、notify...

    Java多线程中ReentrantLock与Condition详解

    主要介绍了Java多线程中ReentrantLock与Condition详解,需要的朋友可以参考下

    【2018最新最详细】并发多线程教程

    【2018最新最详细】并发多线程教程,课程结构如下 1.并发编程的优缺点 2.线程的状态转换以及基本操作 3.java内存模型以及happens-before规则 4.彻底理解synchronized 5.彻底理解volatile 6.你以为你真的了解final吗...

    java 并发编程 多线程

    java 并发 编程 多线程 concurrent lock condition executorserice executor java.util.curcurrent.

    JUC多线程学习个人笔记

    JUC(Java Util Concurrent)是Java中用于并发编程的工具包,提供了一组接口和类,用于处理多线程和并发操作。JUC提供了一些常用的并发编程模式和工具,如线程池、并发集合、原子操作等。 JUC的主要特点包括: ...

    Python多线程编程(七):使用Condition实现复杂同步

    目前我们已经会使用Lock去对公共资源进行互斥访问了,也探讨了同一线程可以使用RLock去重入锁,但是尽管如此我们只不过才处理了一些程序中简单的同步现象,我们甚至还不能很合理的去解决使用Lock锁带来的死锁问题。...

    JAVA多线程实现2个producer和一个Consumer把整数放入到一个环形缓冲Circle Buffer中

    采用同步机制synchronized/wait(notify)或者lock(unlock)/condition variable实现两个producer和一个consumer之间协调运行。运行结果输出格式为:Put(or Get) number {[content] length start_index end_index} 包含...

    Lock锁的底层原理完整版

    Lock锁,一种线程同步机制,其主要功能是防止多个线程同时访问同一代码块,从而避免因并发问题引发的数据不一致或其他错误。...总的来说,Lock锁是Java多线程编程中的重要工具,能够有效保障程序运行的正确性和稳定性。

    Java 7并发编程实战手册

    如果你是一名Java开发人员,并且想进一步掌握并发编程和多线程技术,并挖掘Java 7并发的新特性,那么本书是你的合适之选。 《Java 7并发编程实战手册》 第1章 线程管理 1 1.1 简介 1 1.2 线程的创建和运行...

    python多线程高级锁condition简单用法示例

    多线程编程中如果使用Condition对象代替lock, 能够实现在某个事件触发后才处理数据, condition中含有的方法: – wait:线程挂起,收到notify通知后继续运行 – notify:通知其他线程, 解除其它线程的wai状态 – ...

    locks框架:接口.pdf

    演示如何通过 lock 和 unlock 方法来实现线程的同步和互斥。 可重入性和重入锁: 解释 Lock 接口的可重入性,讲解同一个线程多次获取锁的机制,避免死锁。介绍 ReentrantLock 的实现原理。 Condition 条件变量: ...

    Python线程同步的实现代码

    threading 模块提供的线程同步原语包括:Lock、RLock、Condition、Event、Semaphore等对象。 线程执行 join与setDaemon 子线程在主线程运行结束后,会继续执行完,如果给子线程设置为守护线程(setDaemon=True),...

    汪文君高并发编程实战视频资源全集

     高并发编程第三阶段30讲 使用Condition实现一个多线程下的Producer-Consumer_.mp4  高并发编程第三阶段31讲 JDK8-StampedLock详细介绍-上_.mp4  高并发编程第三阶段32讲 JDK8-StampedLock详细介绍-下.mp4  高...

    汪文君高并发编程实战视频资源下载.txt

     高并发编程第三阶段30讲 使用Condition实现一个多线程下的Producer-Consumer_.mp4  高并发编程第三阶段31讲 JDK8-StampedLock详细介绍-上_.mp4  高并发编程第三阶段32讲 JDK8-StampedLock详细介绍-下.mp4  高...

    嵌入式的多线程应用程序设计

    pthread_mutex_lock(&b-&gt;lock); /* Wait until buffer is not full */ while ((b-&gt;writepos + 1) % BUFFER_SIZE == b-&gt;readpos) { printf("wait for not full\n"); pthread_cond_wait(&b-&gt;notfull, &b-&gt;...

    Java并发编程原理与实战

    了解多线程所带来的安全风险.mp4 从线程的优先级看饥饿问题.mp4 从Java字节码的角度看线程安全性问题.mp4 synchronized保证线程安全的原理(理论层面).mp4 synchronized保证线程安全的原理(jvm层面).mp4 单例问题...

    python线程中同步锁详解

    或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lock Rlock Semaphore Event Condition 用来保证线程之间的同步,后者保证访问...

Global site tag (gtag.js) - Google Analytics