`
Ydoing
  • 浏览: 100484 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java多线程并发编程之构建自定义同步工具

 
阅读更多

当Java类库没有提供适合的同步工具时,就需要构建自定义同步工具。

可阻塞状态依赖操作的结构
  1. 复制代码
    acquir lock on object state;//请求获取锁
    while(precondition does not hold){//没有满足前提条件
       release lock;//先释放锁
       wait until precondition might hold;//等待满足前提条件
       optionlly fail if interrupted or timeout expires;//因为中断或者超时执行失败
       reacquire lock;//重新尝试获取锁
    }
    perform action//执行
       release lock;//释放锁
    复制代码

有界缓存实现基类示例
  1. 复制代码
    public class BaseBoundBuffer<V> {
    private final V[] buf;
    private int tail;
    private int head;
    private int count;
    @SuppressWarnings("unchecked")
    public BaseBoundBuffer(int capacity) {
    buf = (V[]) new Object[capacity];
    }
    public synchronized void doPut(V v) {
    buf[tail] = v;
    if (++tail == buf.length)
    tail = 0;
    count++;
    }
    public synchronized V doTake() {
    V v = buf[head];
    
    if (++head == buf.length)
    head = 0;
    count--;
    return v;
    }
    public final synchronized boolean isFull() {
    return count == buf.length;
    }
    public final synchronized boolean isEmpty() {
    return count == 0;
    }
    }
    复制代码

阻塞实现方式一:抛异常给调用者

  1. public synchronized void put1(V v)  throws Exception{
    if(isFull())
    throw new Exception("full error");
    doPut(v);
    }

分析:异常应该应用于发生异常情况中,在这里抛异常不合适;需要调用者是处理前提条件失败的情况,并没有解决根本问题。

阻塞实现方式二:通过轮询和休眠

  1. 复制代码
    public void put2(V v) throws InterruptedException {
    while (true) {//轮询
    synchronized (this) {
    if (!isFull()) {
    doPut(v);
    return;     
    }
    }
    Thread.sleep(SLEEP_TIME);//休眠
    }
    }
    复制代码

分析:很难权衡休眠时间SLEEP_TIME设置。如果设置过小,CPU可能会轮询多次,消耗CPU资源也越高;如果设置过大,响应性就越低。

阻塞实现方式三:条件队列

条件队列中的元素是一个个等待相关条件的线程。每个Java对象都可以作为一个锁,每个对象同样可以作为一个条件队列并且Object中的wait、notify、notifyAll方法就构成了内部条件队列的API。Object.wait会自动释放锁,并请求操作系统挂起当前线程,从而使其它线程能获得这个锁并修改对象的状态。Object.notify和Object.notifyAll能唤醒正在等待线程,从条件队列中选取一个线程唤醒并尝试重新获取锁。
  1. public synchronized void put3(V v) throws InterruptedException {
    while(isFull())
    wait();
    doput(v);
    notifyAll();
    }

    分析:获得较好响应,简单易用。

使用条件队列​

1.条件谓词

  • 定义:条件谓词是使某个操作成为状态依赖操作的前提条件。条件谓词是由类中各个状态变量构成的表达式。例如,对于put方法的条件谓词就是“缓存不为空”。
  • 关系:在条件等待中存在一种重要的三元关系,包括加锁、wait方法和一个条件谓词。在条件谓词中包含多个状态变量,而每个状态变量必须由一个锁来保护,因此在测试条件谓词之前必须先持有这个锁。锁对象和条件队列对象(及调用wait和notify等方法所在的对象)必须是同一个对象。
  • 约束:每次调用wait都会隐式地和特定的条件谓词相关联,当调用特定条件谓词时,调用者必须已经持有与条件队列相关的锁,这个锁必须还保护这组成条件谓词的状态变量

2.条件队列使用规则

  • 通常都有一个条件谓词
  • 永远在调用wait之前测试条件谓词,并且在wait中返回后再次测试;

  • 永远在循环中调用wait;

  • 确保构成条件谓词的状态变量被锁保护,而这个锁必须与这个条件队列相关联;

  • 当调用wait、notify和notifyAll时,要持有与条件队列相关联的锁;

  • 在检查条件谓词之后,开始执行被保护的逻辑之前,不要释放锁;

3.通知

尽量使用notifyAll,而不是nofify.因为nofify会随机唤醒一个线程从休眠状态变为Blocked状态(Blocked状态是种线程一直处于尝试获取锁的状态,即一旦发现锁可用,马上持有锁),而notifyAll会唤醒条件队列中所有的线程从休眠状态变为Blocked状态.考虑这么种情况,假如线程A因为条件谓词Pa进入休眠状态,线程B因为条件谓词Pb进入休眠状态.这时Pb为真,线程C执行单一的notify.如果JVM随机选择了线程A进行唤醒,那么线程A检查条件谓词Pa不为真后又进入了休眠状态.从这以后再也没有其它线程能被唤醒,程序会一直处于休眠状态.如果使用notifyAll就不一样了,JVM会唤醒条件队列中所有等待线程从休眠状态变为Blocked状态,即使随机选出一个线程一因为条件谓词不为真进入休眠状态,其它线程也会去竞争锁从而继续执行下去.

4.状态依赖方法的标准形式

复制代码
void stateDependentMethod throwsInterruptedException{
synchronized(lock){
while(!conditionPredicate))
lock.wait();
}
//dosomething();
....

notifyAll();
}
复制代码

显示Condition对象

显示的Condition对象是一种更灵活的选择,提供了更丰富的功能:在每个锁上可以存在多个等待,条件等待可以是中断的获不可中断的,基于时限的等待,以及公平的或非公平的队列操作。一个Condition可以和一个Lock关联起来,就像一个条件队列和一个内置锁关联起来一样。要创建一个Condition,可以在相关联的Lock上调用Lock.newCondition方法。以下用显示条件变量重新实现有界缓存

public class ConditionBoundedBuffer<V> {
	private final V[] buf;
	private int tail;
	private int head;
	private int count;
	private Lock lock = new ReentrantLock();
	private Condition notFullCondition = lock.newCondition();
	private Condition notEmptyCondition = lock.newCondition();
	@SuppressWarnings("unchecked")
	public ConditionBoundedBuffer(int capacity) {
		buf = (V[]) new Object[capacity];
	}

	public void doPut(V v) throws InterruptedException {
		try {
			lock.lock();
			while (count == buf.length)
				notFullCondition.await();
			buf[tail] = v;
			if (++tail == buf.length)
				tail = 0;
			count++;
			notEmptyCondition.signal();
		} finally {
			lock.unlock();
		}

	}

	public V doTake() throws InterruptedException {
		try {
			lock.lock();
			while (count == 0)
				notEmptyCondition.await();
			V v = buf[head];
			buf[head] = null;
			if (++head == buf.length)
				head = 0;
			count--;
			notFullCondition.signal();
			return v;
		} finally {
			lock.unlock();
		}
	}
}


版权声明:本文为博主原创文章,未经博主允许不得转载。

分享到:
评论

相关推荐

    Java并发编程实战

    第1章 简介 1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 ...第14章 构建自定义的同步工具 第15章 原子变量与非阻塞同步机制 第16章 Java内存模型 附录A 并发性标注 参考文献

    Java并发编程实战.pdf

    如何提高单线程子系统的响应性,如何确保并发程序执行预期任务,如何提高并发代码的性能和可伸缩性等内容,最后介绍了一些高级主题,如显式锁、原子变量、非阻塞算法以及如何开发自定义的同步工具类。

    java并发编程:juc、aqs

    在现代软件开发中,多核处理器的普及使得并发编程成为开发者不可忽视的技能。Java 并发编程中的 JUC(java.util....深入理解和掌握 JUC 和 AQS,可以让开发者更好地应对多线程并发编程中的挑战,提高程序性能和稳定性。

    Java并发编程实践

    《Java并发编程实战》深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及...

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

    │ 高并发编程第一阶段05讲、采用多线程方式模拟银行排队叫号.mp4 │ 高并发编程第一阶段06讲、用Runnable接口将线程的逻辑执行单元从控制中抽取出来.mp4 │ 高并发编程第一阶段07讲、策略模式在Thread和Runnable...

    Java 并发编程实战

    前 言 第1章 简介 1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 1.2.2 建模的简单性 ...第14章 构建自定义的同步工具 第15章 原子变量与非阻塞同步机制 第16章 Java内存模型 附录A 并发性标注

    Java并发编程实战-读书笔记

    《Java并发编程实战》个人读书笔记,非常详细: 1 简介 2 线程安全性 3 对象的共享 4 对象的组合 5 基础构建模块 6 任务执行 ...14 构建自定义的同步工具 15 原子变量与非阻塞同步机制 16 Java内存模型

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

    │ 高并发编程第一阶段05讲、采用多线程方式模拟银行排队叫号.mp4 │ 高并发编程第一阶段06讲、用Runnable接口将线程的逻辑执行单元从控制中抽取出来.mp4 │ 高并发编程第一阶段07讲、策略模式在Thread和Runnable...

    Java并发编程(学习笔记).xmind

    Java并发编程 背景介绍 并发历史 必要性 进程 资源分配的最小单位 线程 CPU调度的最小单位 线程的优势 (1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 ...

    JAVA上百实例源码以及开源项目

     Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...

    JAVA上百实例源码以及开源项目源代码

     Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...

    java开源包4

    Java多线程程序死锁检查 JCarder JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 Java...

    java开源包11

    Java多线程程序死锁检查 JCarder JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 Java...

    java开源包6

    Java多线程程序死锁检查 JCarder JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 Java...

    java开源包101

    Java多线程程序死锁检查 JCarder JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 Java...

    java开源包9

    Java多线程程序死锁检查 JCarder JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 Java...

    java开源包8

    Java多线程程序死锁检查 JCarder JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 Java...

Global site tag (gtag.js) - Google Analytics