`
uule
  • 浏览: 6387591 次
  • 性别: Icon_minigender_1
  • 来自: 一片神奇的土地
社区版块
存档分类
最新评论

LinkedBlockingQueue + 单向链表基本结构

 
阅读更多

LinkedBlockingQueue是一个单向链表结构的队列,也就是只有next,没有prev。如果不指定容量默认为Integer.MAX_VALUE。通过putLock和takeLock两个锁进行同步,两个锁分别实例化notFull和notEmpty两个Condtion,用来协调多线程的存取动作。其中某些方法(如remove,toArray,toString,clear等)的同步需要同时获得这两个锁,此时存或者取操作都会不可进行,需要注意的是所有需要同时lock的地方顺序都是先putLock.lock再takeLock.lock,这样就避免了可能出现的死锁问题。

takeLock实例化出一个notEmpty的Condition,putLock实例化一个notFull的Condition,两个Condition协调即时通知线程队列满与不满的状态信息。

 

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    /**
     * 节点数据结构
     */
    static class Node<E> {
        /** The item, volatile to ensure barrier separating write and read */
        volatile E item;
        Node<E> next;
        Node(E x) { item = x; }
    }

    /** 队列的容量 */
    private final int capacity;

    /** 持有节点计数器 */
    private final AtomicInteger count = new AtomicInteger(0);

    /** 头指针 */
    private transient Node<E> head;

    /** 尾指针 */
    private transient Node<E> last;

    /** 用于读取的独占锁*/
    private final ReentrantLock takeLock = new ReentrantLock();

    /** 队列是否为空的条件 */
    private final Condition notEmpty = takeLock.newCondition();

    /** 用于写入的独占锁 */
    private final ReentrantLock putLock = new ReentrantLock();

    /** 队列是否已满的条件 */
    private final Condition notFull = putLock.newCondition();

    private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }

    private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            notFull.signal();
        } finally {
            putLock.unlock();
        }
    }
       //插入数据到链尾
    private void insert(E x) {
        last = last.next = new Node<E>(x);
    }
       //从链头取数据
    private E extract() {
        Node<E> first = head.next;
        head = first;
        E x = first.item;
        first.item = null;
        return x;
    }

    private void fullyLock() {
        putLock.lock();
        takeLock.lock();
    }

    private void fullyUnlock() {
        takeLock.unlock();
        putLock.unlock();
    }

    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);  //注意初始化时head/last地址相同,数据都为null
					// 其实就相当于只有一个节点
    }

   ...
}

 这里仅仅展示部分源码,主要的方法在后面的分析中列出。分析之前明确一个最基本的概念。天天念叨着编写线程安全的类,什么是线程安全的类?那就是类内共享的全局变量的访问必须保证是不受多线程形式影响的。如果由于多线程的访问(改变,遍历,查看)而使这些变量结构被破坏或者针对这些变量操作的原子性被破坏,则这个类的编写不是线程安全的。 


    明确了这个基本的概念就可以很好的理解这个Queue的实现为什么是线程安全的了。在LinkedBlockingQueue的所有共享的全局变量中,final声明的capacity在构造器生成实例时就成了不变量了。而final声明的count由于是AtomicInteger类型的,所以能够保证其操作的原子性。剩下的final的变量都是初始化成了不变量,并且不包含可变属性,所以都是访问安全的。那么剩下的就是Node类型的head和last两个可变量。所以要保证LinkedBlockingQueue是线程安全的就是要保证对head和last的访问是线程安全的。 

    首先从上面的源码可以看到insert(E x),extract()是真正的操作head,last来入队和出对的方法,但是由于是私有的,所以不能被直接访问,不用担心线程的问题。实际入队的公开的方法是put(E e),offer(E e)和offer(E e, long timeout, TimeUnit unit)。put(...)方法与offer(...)都是把新元素加入到队尾,所不同的是如果不满足条件put会把当前执行的线程扔到等待集中等待被唤醒继续执行,而offer则是直接退出,所以如果是需要使用它的阻塞特性的话,不能直接使用poll(...)。 
    
    put(...)方法中加入元素的操作使用this.putLock来限制多线程的访问,并且使用了可中断的方式: 

  1. public void put(E e) throws InterruptedException {  
  2.         if (e == nullthrow new NullPointerException();  
  3.         int c = -1;  
  4.         final ReentrantLock putLock = this.putLock;  
  5.         final AtomicInteger count = this.count; //----------------a  
  6.         putLock.lockInterruptibly();//随时保证响应中断 //--------b  
  7.         try {  
  8.             //*****************************(1)*********************************  
  9.             try {  
  10.                 while (count.get() == capacity)  
  11.                     notFull.await();  
  12.             } catch (InterruptedException ie) {  
  13.                 notFull.signal(); // propagate to a non-interrupted thread  
  14.                 throw ie;  
  15.             }  
  16.            //*****************************end*********************************  
  17.             insert(e);//真正的入队操作  
  18.            //********************(2)**********************  
  19.             c = count.getAndIncrement();  
  20.             if (c + 1 < capacity)  
  21.                 notFull.signal();  
  22.             //******************end**********************  
  23.         } finally {  
  24.             putLock.unlock();  
  25.         } //-------------------------c  
  26.         if (c == 0//---------------d  
  27.             signalNotEmpty();  
  28. }  


代码段(1)是阻塞操作,代码段(2)是count递增和唤醒等待的操作。两者之间的insert(e)才是入队操作,其实际是操作的队尾引用last,并且没有牵涉到head。所以设计两个锁的原因就在这里!因为出队操作take(),poll()实际是执行extract()仅仅操作队首引用head。增加了this.takeLock这个锁,就实现了多个不同任务的线程入队的同时可以进行出对的操作,并且由于两个操作所共同使用的count是AtomicInteger类型的,所以完全不用考虑计数器递增递减的问题。假设count换成int,则相应的putLock内的count++和takeLock内的count--有可能相互覆盖,最终造成count的值被腐蚀,故这种设计必须使用原子操作类。 
      我之前说过,保证类的线程安全只要保证head和last的操作的线程安全,也就是保证insert(E x)和extract()线程安全即可。那么上面的put方法中的代码段(1)放在a,b之间,代码段(2)放在c,d之间不是更好?毕竟锁的粒度越小越好。单纯的考虑count的话这样的改变是正确的,但是await()和singal()这两个方法执行时都会检查当前线程是否是独占锁的那个线程,如果不是则抛出java.lang.IllegalMonitorStateException异常。而这两段代码中包含notFull.await()和notFull.signal()这两句使得(1),(2)必须放在lock保护块内。这里说明主要是count本身并不需要putLock或者takeLock的保护,从   

  1. public int size() {  
  2.         return count.get();  
  3. }  


可以看出count的访问是不需要任何锁的。而在put等方法中,其与锁机制的混用很容易造成迷惑。最后put中的代码d的作用主要是一个低位及时通知的作用,也就是队列刚有值试图获得takeLock去通知等待集中的出队线程。因为c==0意味着count.getAndIncrement()原子递增成功,所以count > 0成立。类似作用的代码: 

  1. if (c == capacity)  
  2.        signalNotFull();  


在take和poll中也有出现,实现了高位及时通知。 
    分析完了put,对应的offer,take,poll方法都是类似的实现。

 

public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        if (count.get() == capacity)
            return false;
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            if (count.get() < capacity) {
                insert(e);
                c = count.getAndIncrement();
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }

 

 

public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            try {
                while (count.get() == 0)
                    notEmpty.await();
            } catch (InterruptedException ie) {
                notEmpty.signal(); // propagate to a non-interrupted thread
                throw ie;
            }

            x = extract();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
 

下面看看遍历队列的操作: 

  1. public Object[] toArray() {  
  2.         fullyLock();  
  3.         try {  
  4.             int size = count.get();  
  5.             Object[] a = new Object[size];  
  6.             int k = 0;  
  7.             for (Node<E> p = head.next; p != null; p = p.next)  
  8.                 a[k++] = p.item;  
  9.             return a;  
  10.         } finally {  
  11.             fullyUnlock();  
  12.         }  
  13. }  


这个方法很简单主要是要清楚一点:这个操作执行时不允许其他线程再修改队首和队尾,所以使用了fullyLock去获取putLock和takeLock,只要成功则可以保证不会再有修改队列的操作。然后就是安心的遍历到最后一个元素为止了。 

    另外在offer(E e, long timeout, TimeUnit unit)这个方法中提供了带有超时的入队操作,如果一直不成功的话,它会尝试在timeout的时间内入队: 

  1. for (;;) {  
  2.      ...//入队操作  
  3.      if (nanos <= 0)  
  4.          return false;  
  5.      try {  
  6.           nanos = notFull.awaitNanos(nanos);  
  7.      } catch (InterruptedException ie) {  
  8.            notFull.signal(); // propagate to a non-interrupted thread  
  9.            throw ie;  
  10.      }  
  11. }  


其内部循环使用notFull.awaitNanos(nanos)方法反复的计算剩余时间的大概值用于实现延时功能。nanos<=0则放弃尝试,直接退出。 

    整体而言,LinkedBlockingQueue的实现还是很清晰的。这些看似复杂的数据结构的实现实质都是多线程的基础的综合应用。就好像数学中千变万化的难题其实都是基础公式的组合一样,如果有清晰的基础认知,还是能找到自己分析的思路的。

来源:http://yanxuxin.iteye.com/blog/582162

 

 

JDK:

方法区别:

 

 boolean offer(E e) 

          将指定元素插入到此队列的尾部(如果立即可行且不会超出此队列的容量),在成功时返回 true,如果此队列已满,则返回 false。 

 boolean offer(E e, long timeout, TimeUnit unit) 

          将指定元素插入到此队列的尾部,如有必要,则等待指定的时间以使空间变得可用。 

 void put(E e) 

          将指定元素插入到此队列的尾部,如有必要,则等待空间变得可用。 


 E peek() 

          获取但不移除此队列的头;如果此队列为空,则返回 null。 

 E poll() 

          获取并移除此队列的头,如果此队列为空,则返回 null。 

 E poll(long timeout, TimeUnit unit) 

          获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。 

 E take() 

          获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。 


 boolean remove(Object o) 

          从此队列移除指定元素的单个实例(如果存在)。 

 int size() 

          返回队列中的元素个数。 


 Object[] toArray() 

          返回按适当顺序包含此队列中所有元素的数组。 

T[]  toArray(T[] a) 

          返回按适当顺序包含此队列中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。 

 String toString() 

          返回此 collection 的字符串表示形式。 


 

 

问题思考:为什么需要用两个锁?

 

单向链表数据结构:

 

package tags;

import java.util.Iterator;

/*
 * 单向链表从头部插数据,从尾部取数据
 * @author Robby
 */
public class SingleLinked<E> {

	class Node<E>{
		volatile E item;
		Node<E> next;
		Node(E x){
			item = x;
		}
	}
	
	private Node<E> head;
	private Node<E> last;
	private static int count;
	
	SingleLinked(){
		last = head = new Node<E>(null);
			//初始化head/last,此时这两个地址相同,其实就是一个节点
	}
	
	private void insert(E e){
		Node<E> newNode = new Node<E>(e);
		last.next = newNode;
		last = last.next;
		count++;
	}
	
	private E extract(){
		//Node<E> first = head.next; //head为null
		//E e = first.item;
		head = head.next;   //只一句就可以
		//first.item = null;
		count--;
		return null;
	}
	
	private void remove(E e){
		if(e == null) return;
		Node<E> pre = head;  //这两个变量用于记录e的节点和前节点
		Node<E> current = head.next;
		/*for(Node<E> p = current; p != null; p = p.next){
			if(p.item.equals(e)){
				pre.next = current.next;
				count--;
				break;
			}
			pre = p;
			current = p.next;
		}*/
		while(current != null){
			if(current.item.equals(e)){
				pre.next = current.next;
				count--;break;
			}
			pre = current;
			current = current.next;
		}
	}
	
	public E peek(){  //从头部弹出一个数据
		Node<E> first = head.next;
		if(first == null) return null;
		return first.item;
	} 
	
	public Object[] toArray(){
		if(count == 0) return null;
		Object[] o = new Object[count];
		int i = 0;
		for(Node<E> p = head.next; p!=null; p = p.next){
			o[i++] = p.item;
		}
		return o;
	}
	
	public void clear(){
		head.next = null;
	    last = head;
		count = 0;
	}
	
	//遍历方法是通过新建一个Iterator实现的
	public Iterator<E> iterator(){
		return new Itr();
	}
	
	private class Itr implements Iterator{

		private Node<E> current;
		private E element;
		
		Itr(){
			current = head.next;
			element = current.item;
		}
		
		public boolean hasNext() {
			return current != null;
		}

		public E next() {
			E e = element;
			current = current.next;
			if(current != null)
				element = current.item;
			return e;
		}

		public void remove() {
			
		}
		
	}
	
	
	public String toString(){
		Object[] o = toArray();
		String str = "";
		if(o == null) return str;
		for(Object ob : o){
			str += ob != null && !ob.toString().equals("") ? ob.toString()+"," : "";
		}
		return str.substring(0,str.length() - 1);
	}
	
	
	
	@SuppressWarnings("unchecked")
	public  static void main(String[] args){
		SingleLinked slink = new SingleLinked();
		slink.insert(1);
		System.out.println("count:"+count+" |"+slink.toString());
		slink.insert(2);
		System.out.println("count:"+count+" |"+slink.toString());
		slink.insert(3);
		System.out.println("count:"+count+" |"+slink.toString());
		
		slink.extract();
		System.out.println("count:"+count+" |"+slink.toString());
		slink.insert(5);
		System.out.println("count:"+count+" |"+slink.toString());
		//slink.extract();
		//System.out.println("count:"+count+" |"+slink.toString());
		
		//slink.clear();
		/*slink.remove(3);
		System.out.println("count:"+count+" |"+slink.toString());
		slink.remove(5);
		System.out.println("count:"+count+" |"+slink.toString());
		slink.remove(2);
		System.out.println("count:"+count+" |"+slink.toString());*/
		Iterator it = slink.iterator();
		while(it.hasNext()){
			System.out.print(it.next()+" ");
		}
	}
	
}

 基本结构其实并不复杂,LinkedBlockingQueue是在此基础上加入了多线程的实现。

 

 

 

分享到:
评论

相关推荐

    Matlab环境下决策分类树的构建、优化与应用

    内容概要:本文详细介绍了如何利用Matlab构建、优化和应用决策分类树。首先,讲解了数据准备阶段,将数据与程序分离,确保灵活性。接着,通过具体实例展示了如何使用Matlab内置函数如fitctree快速构建决策树模型,并通过可视化工具直观呈现决策树结构。针对可能出现的过拟合问题,提出了基于成本复杂度的剪枝方法,以提高模型的泛化能力。此外,还分享了一些实用技巧,如处理连续特征、保存模型、并行计算等,帮助用户更好地理解和应用决策树。 适合人群:具有一定编程基础的数据分析师、机器学习爱好者及科研工作者。 使用场景及目标:适用于需要进行数据分类任务的场景,特别是当需要解释性强的模型时。主要目标是教会读者如何在Matlab环境中高效地构建和优化决策分类树,从而应用于实际项目中。 其他说明:文中不仅提供了完整的代码示例,还强调了代码模块化的重要性,便于后续维护和扩展。同时,对于初学者来说,建议从简单的鸢尾花数据集开始练习,逐步掌握决策树的各项技能。

    《营销调研》第7章-探索性调研数据采集.pptx

    《营销调研》第7章-探索性调研数据采集.pptx

    Assignment1_search_final(1).ipynb

    Assignment1_search_final(1).ipynb

    美团外卖优惠券小程序 美团优惠券微信小程序 自带流量主模式 带教程.zip

    美团优惠券小程序带举牌小人带菜谱+流量主模式,挺多外卖小程序的,但是都没有搭建教程 搭建: 1、下载源码,去微信公众平台注册自己的账号 2、解压到桌面 3、打开微信开发者工具添加小程序-把解压的源码添加进去-appid改成自己小程序的 4、在pages/index/index.js文件搜流量主广告改成自己的广告ID 5、到微信公众平台登陆自己的小程序-开发管理-开发设置-服务器域名修改成

    《计算机录入技术》第十八章-常用外文输入法.pptx

    《计算机录入技术》第十八章-常用外文输入法.pptx

    基于Andorid的跨屏拖动应用设计.zip

    基于Andorid的跨屏拖动应用设计实现源码,主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。

    《网站建设与维护》项目4-在线购物商城用户管理功能.pptx

    《网站建设与维护》项目4-在线购物商城用户管理功能.pptx

    区块链_房屋转租系统_去中心化存储_数据防篡改_智能合约_S_1744435730.zip

    区块链_房屋转租系统_去中心化存储_数据防篡改_智能合约_S_1744435730

    《计算机应用基础实训指导》实训五-Word-2010的文字编辑操作.pptx

    《计算机应用基础实训指导》实训五-Word-2010的文字编辑操作.pptx

    《移动通信(第4版)》第5章-组网技术.ppt

    《移动通信(第4版)》第5章-组网技术.ppt

    ABB机器人基础.pdf

    ABB机器人基础.pdf

    《综合布线施工技术》第9章-综合布线实训指导.ppt

    《综合布线施工技术》第9章-综合布线实训指导.ppt

    最新修复版万能镜像系统源码-最终版站群利器持续更新升级

    很不错的一套站群系统源码,后台配置采集节点,输入目标站地址即可全自动智能转换自动全站采集!支持 https、支持 POST 获取、支持搜索、支持 cookie、支持代理、支持破解防盗链、支持破解防采集 全自动分析,内外链接自动转换、图片地址、css、js,自动分析 CSS 内的图片使得页面风格不丢失: 广告标签,方便在规则里直接替换广告代码 支持自定义标签,标签可自定义内容、自由截取、内容正则截取。可以放在模板里,也可以在规则里替换 支持自定义模板,可使用标签 diy 个性模板,真正做到内容上移花接木 调试模式,可观察采集性能,便于发现和解决各种错误 多条采集规则一键切换,支持导入导出 内置强大替换和过滤功能,标签过滤、站内外过滤、字符串替换、等等 IP 屏蔽功能,屏蔽想要屏蔽 IP 地址让它无法访问 ****高级功能*****· url 过滤功能,可过滤屏蔽不采集指定链接· 伪原创,近义词替换有利于 seo· 伪静态,url 伪静态化,有利于 seo· 自动缓存自动更新,可设置缓存时间达到自动更新,css 缓存· 支持演示有阿三源码简繁体互转· 代理 IP、伪造 IP、随机 IP、伪造 user-agent、伪造 referer 来路、自定义 cookie,以便应对防采集措施· url 地址加密转换,个性化 url,让你的 url 地址与众不同· 关键词内链功能· 还有更多功能等你发现…… 程序使用非常简单,仅需在后台输入一个域名即可建站,不限子域名,站群利器,无授权,无绑定限制,使用后台功能可对页面进行自定义修改,在程序后台开启生 成功能,只要访问页面就会生成一个本地文件。当用户再次访问的时候就直接访问网站本地的页面,所以目标站点无法访问了也没关系,我们的站点依然可以访问, 支持伪静态、伪原创、生成静态文件、自定义替换、广告管理、友情链接管理、自动下载 CSS 内的图。

    《Approaching(Almost)any machine learning problem》中文版第11章

    【自然语言处理】文本分类方法综述:从基础模型到深度学习的情感分析系统设计

    基于Andorid的下拉浏览应用设计.zip

    基于Andorid的下拉浏览应用设计实现源码,主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。

    P2插电式混合动力系统Simulink模型:基于逻辑门限值控制策略的混动汽车仿真

    内容概要:本文详细介绍了一个原创的P2插电式混合动力系统Simulink模型,该模型基于逻辑门限值控制策略,涵盖了多个关键模块如工况输入、驾驶员模型、发动机模型、电机模型、制动能量回收模型、转矩分配模型、运行模式切换模型、档位切换模型以及纵向动力学模型。模型支持多种标准工况(WLTC、UDDS、EUDC、NEDC)和自定义工况,并展示了丰富的仿真结果,包括发动机和电机转矩变化、工作模式切换、档位变化、电池SOC变化、燃油消耗量、速度跟随和最大爬坡度等。此外,文章还深入探讨了逻辑门限值控制策略的具体实现及其效果,提供了详细的代码示例和技术细节。 适合人群:汽车工程专业学生、研究人员、混动汽车开发者及爱好者。 使用场景及目标:①用于教学和科研,帮助理解和掌握P2混动系统的原理和控制策略;②作为开发工具,辅助设计和优化混动汽车控制系统;③提供仿真平台,评估不同工况下的混动系统性能。 其他说明:文中不仅介绍了模型的整体架构和各模块的功能,还分享了许多实用的调试技巧和优化方法,使读者能够更好地理解和应用该模型。

    电力系统分布式调度中ADMM算法的MATLAB实现及其应用

    内容概要:本文详细介绍了基于ADMM(交替方向乘子法)算法在电力系统分布式调度中的应用,特别是并行(Jacobi)和串行(Gauss-Seidel)两种不同更新模式的实现。文中通过MATLAB代码展示了这两种模式的具体实现方法,并比较了它们的优劣。并行模式适用于多核计算环境,能够充分利用硬件资源,尽管迭代次数较多,但总体计算时间较短;串行模式则由于“接力式”更新机制,通常收敛更快,但在计算资源有限的情况下可能会形成瓶颈。此外,文章还讨论了惩罚系数rho的自适应调整策略以及在电-气耦合系统优化中的应用实例。 适合人群:从事电力系统优化、分布式计算研究的专业人士,尤其是有一定MATLAB编程基础的研究人员和技术人员。 使用场景及目标:①理解和实现ADMM算法在电力系统分布式调度中的应用;②评估并行和串行模式在不同应用场景下的性能表现;③掌握惩罚系数rho的自适应调整技巧,提高算法收敛速度和稳定性。 其他说明:文章提供了详细的MATLAB代码示例,帮助读者更好地理解和实践ADMM算法。同时,强调了在实际工程应用中需要注意的关键技术和优化策略。

    这篇文章详细探讨了交错并联Buck变换器的设计、仿真及其实现,涵盖了从理论分析到实际应用的多个方面(含详细代码及解释)

    内容概要:本文深入研究了交错并联Buck变换器的工作原理、性能优势及其具体实现。文章首先介绍了交错并联Buck变换器相较于传统Buck变换器的优势,包括减小输出电流和电压纹波、降低开关管和二极管的电流应力、减小输出滤波电容容量等。接着,文章详细展示了如何通过MATLAB/Simulink建立该变换器的仿真模型,包括参数设置、电路元件添加、PWM信号生成及连接、电压电流测量模块的添加等。此外,还探讨了PID控制器的设计与实现,通过理论分析和仿真验证了其有效性。最后,文章通过多个仿真实验验证了交错并联Buck变换器在纹波性能、器件应力等方面的优势,并分析了不同控制策略的效果,如P、PI、PID控制等。 适合人群:具备一定电力电子基础,对DC-DC变换器特别是交错并联Buck变换器感兴趣的工程师和技术人员。 使用场景及目标:①理解交错并联Buck变换器的工作原理及其相对于传统Buck变换器的优势;②掌握使用MATLAB/Simulink搭建交错并联Buck变换器仿真模型的方法;③学习PID控制器的设计与实现,了解其在电源系统中的应用;④通过仿真实验验证交错并联Buck变换器的性能,评估不同控制策略的效果。 其他说明:本文不仅提供了详细的理论分析,还给出了大量可运行的MATLAB代码,帮助读者更好地理解和实践交错并联Buck变换器的设计与实现。同时,通过对不同控制策略的对比分析,为实际工程应用提供了有价值的参考。

    《综合布线施工技术》第8章-综合布线工程案例.ppt

    《综合布线施工技术》第8章-综合布线工程案例.ppt

    基于STM32F103C8T6的K型热电偶温度控制仪设计与实现

    内容概要:本文详细介绍了基于STM32F103C8T6的K型热电偶温度控制仪的设计与实现。硬件部分涵盖了热电偶采集电路、OLED显示模块、蜂鸣器电路、风扇控制电路以及EEPROM存储模块。软件部分则涉及ADC配置、OLED刷新、PID控温算法、EEPROM参数存储、风扇PWM控制等多个方面的具体实现。文中不仅提供了详细的代码示例,还分享了许多调试经验和注意事项,如冷端补偿、DMA传输优化、I2C时钟配置、PWM频率选择等。 适合人群:具有一定嵌入式系统开发经验的工程师和技术爱好者。 使用场景及目标:适用于需要进行温度监测与控制的应用场景,如工业自动化、实验室设备等。目标是帮助读者掌握STM32F103C8T6在温度控制领域的应用技巧,提升硬件设计和软件编程能力。 其他说明:本文提供的工程文件包含Altium Designer的原理图PCB文件,便于二次开发。此外,文中还提到了一些扩展功能,如加入Modbus通信协议,供有兴趣的读者进一步探索。

Global site tag (gtag.js) - Google Analytics