`
Ydoing
  • 浏览: 106051 次
  • 性别: 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并发编程中构建自定义同步工具

    在Java并发编程中,构建自定义同步工具是解决多线程环境下共享数据访问问题的关键。以下将详细讨论几个核心知识点: 1. 可阻塞状态依赖操作的结构 这种结构通常用于确保线程安全,即在不满足特定前提条件时,线程...

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

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

    Java并发编程实战华章专业开发者书库 (Tim Peierls 等 美Brian Goetz).pdf

    《Java并发编程实战》是一本深入探讨Java平台并发编程的权威指南,由Tim Peierls等人与Brian Goetz合著,旨在帮助Java开发者理解和掌握在多线程环境中编写高效、安全的代码。这本书由拥有丰富经验的JDK并发大师及...

    Java并发编程实战

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

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

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

    java多线程设计

    总结,Java多线程设计是构建高性能、高并发应用的基础。通过理解并合理使用不可变对象,我们可以有效预防多线程环境中的非安全问题,确保程序的稳定性和正确性。在实际开发中,结合各种线程同步机制和并发工具,可以...

    Java并发编程实战.pdf

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

    java 并发编程实践 英文版 English

    总之,《Java并发编程实践》是一本全面、深入的指南,旨在帮助Java开发者掌握并发编程的核心概念和技术,无论是在设计、编码、调试还是维护多线程Java程序方面,都是一本不可或缺的参考书籍。随着摩尔定律效应逐渐...

    Java并发编程:设计原则与模式(第二版)-3

    《Java并发编程:设计原则与模式(第二版)》是一本深入探讨Java多线程编程技术的权威著作。这本书详细阐述了在Java平台中进行高效并发处理的关键概念、设计原则和实用模式。以下是对该书内容的一些核心知识点的概述...

    Java多线程编程总结

    Java多线程编程是构建高性能、高并发应用程序的基础,涉及线程的创建、管理、同步、调度等多个方面。掌握线程生命周期、同步机制、并发工具和最佳实践是成为一名优秀Java开发者的关键。通过深入理解并熟练运用Java多...

    Java并发编程实践

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

    java并发编程实践 pdf

    ### Java多线程基础 #### 创建线程 在Java中创建线程有两种基本方式:继承Thread类或者实现Runnable接口。这两种方式各有优缺点: - **继承Thread类**:直接继承Thread类并重写run()方法,这种方式简单明了。 - **...

    Java 并发编程实战

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

    Java-并发(Concurrent)编程

    Java并发编程是开发高效应用程序的关键技能之一,尤其在如今的多核处理器环境下,理解并熟练掌握并发技术至关重要。本文将深入探讨并发编程的核心概念、工具和实现机制。 首先,我们来了解一下多线程。多线程是编程...

    JAVA并发编程实践

    本书深入浅出地介绍了Java平台上的并发编程原理和最佳实践,旨在帮助读者构建高效、可扩展且线程安全的应用。 在Java中,并发编程主要依赖于JVM(Java虚拟机)提供的线程机制,包括Thread类、Runnable接口以及...

    java多线程网络编程实现ATM自动取款机系统

    Java多线程网络编程在实现ATM自动取款机系统中的应用主要涉及到以下几个核心知识点: 1. **Java多线程**:多线程是Java语言的重要特性,它允许多个任务在同一时间执行,提高程序效率。在ATM系统中,每个用户操作...

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

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

    java并发测试

    `MutiThreadTest.java` 可能会使用这些工具来模拟并行执行的任务,测试目标接口在多线程环境下的行为。 在并发测试中,我们通常关注以下几个方面: 1. **性能测试**:衡量多线程环境下接口的响应时间和吞吐量,以...

Global site tag (gtag.js) - Google Analytics