一文带你彻底把握阻塞队列-从概念到实战 (彻底彻底)
一、摘要
在之前的文章中,咱们引见了消费者和消费者模型的最基本成功思绪,置信大家对它曾经有一个初步的意识。
在的并发包外面还有一个十分关键的接口:BlockingQueue。
BlockingQueue是一个阻塞队列,更为准确的解释是:BlockingQueue是一个基于阻塞机制成功的线程安保的队列。经过它也可以成功消费者和消费者模型,并且效率更高、安保牢靠,相比之前引见的消费者和消费者模型,它可以同时成功消费者和消费者并行运转。
那什么是阻塞队列呢?
繁难的说,就是当参数在入队和出队时,经过加锁的形式来防止线程并发操作时造成的数据意外疑问。
在Java中,能对线程并发口头启动加锁的形式关键有synchronized和ReentrantLock,其中BlockingQueue驳回的是ReentrantLock形式成功。
与此对应的还有非阻塞机制的队列,关键是驳回CAS形式来控制并发操作,例如:ConcurrentLinkedQueue,这个咱们在前面的文章再启动分享引见。
当天咱们关键引见BlockingQueue关系的常识和用法,废话不多说了,进入正题!
二、BlockingQueue方法引见
关上BlockingQueue的源码,你会发现它承袭自Queue,正如上文提到的,它实质是一个队列接口。
publicinterfaceBlockingQueue<E>extendsQueue<E>{//...省略}
关于队列,咱们在之前的汇合系列文章中对此有过深化的引见,本篇就再次繁难的引见一下。
队列其实是一个数据结构,元素遵照先进先出的准绳,一切新元素的拔出,也被称为入队操作,会拔出到队列的尾部;元素的移除,也被称为出队操作,会从队列的头部开局移除,从而保障先进先出的准绳。
在Queue接口中,总共有6个方法,可以分为3类,区分是:拔出、移除、查问,内容如下:
方法形容add(e)拔出元素,假设拔出失败,就抛意外offer(e)拔出元素,假设拔出成功,就前往true;反之falseremove()移除元素,假设移除失败,就抛意外poll()移除元素,假设移除成功,前往true;反之falseelement()失掉队首元素,假设失掉结果为空,就抛意外peek()失掉队首元素,假设失掉结果为空,前往空对象
由于BlockingQueue是Queue的子接口,了解Queue接口外面的方法,有助于咱们对BlockingQueue的了解。
除此之外,BlockingQueue还独自裁减了一些特有的方法,内容如下:
方法形容put(e)拔出元素,假设没有拔出成功,线程会不时阻塞,直到队列中有空间再继续offer(e,time,unit)拔出元素,假设在指定的期间内没有拔出成功,就前往false;反之truetake()移除元素,假设没有移除成功,线程会不时阻塞,直到队列中新的数据被参与poll(time,unit)移除元素,假设在指定的期间内没有移除成功,就前往false;反之truedrnTo(Collectionc,intmaxElements)一次性性取走队列中的数据到c中,可以指定取的个数。该方法可以优化失掉数据效率,不须要屡次分批加锁或监禁锁
剖析源码,你会发现相比个别的Queue子类,BlockingQueue子类关键有以下几个显著的不同点:
三、BlockingQueue用法详解
关上源码,BlockingQueue接口的成功类十分多,咱们重点解说一下其中的5个十分关键的成功类,区分如下表所示。
成功类配置ArrayBlockingQueue基于数组的阻塞队列,经常使用数组存储数据,须要指定长度,所以是一个有界队列LinkedBlockingQueue基于链表的阻塞队列,经常使用链表存储数据,自动是一个无界队列;也可以经过结构方法中的capacity设置最大元素数量,所以也可以作为有界队列SynchronousQueue一种没有缓冲的队列
消费者发生的数据间接会被消费者失掉并且立刻消费PriorityBlockingQueue基于优先级别的阻塞队列,底层基于数组成功,是一个无界队列DelayQueue提前队列,其中的元素只要到了其指定的提后期间,能力够从队列中出队
上方咱们对以上成功类的用法,启动逐一引见。
3.1、ArrayBlockingQueue
ArrayBlockingQueue是一个基于数组的阻塞队列,初始化的时刻肯定指定队列大小,源码成功比拟繁难,驳回的是ReentrantLock和Condition成功消费者和消费者模型,局部外围源码如下:
publicclassArrayBlockingQueue<E>extendsAbstractQueue<E>implementsBlockingQueue<E>,java.io.Serializable{/**经常使用数组存储队列中的元素*/finalObject[]items;/**经常使用独占锁ReetrantLock*/finalReentrantLocklock;/**期待出队的条件*/privatefinalConditionnotEmpty;/**期待入队的条件*/privatefinalConditionnotFull;/**初始化时,须要指定队列大小*/publicArrayBlockingQueue(intcapacity){this(capacity,false);}/**初始化时,也指出指定能否为偏心锁,*/publicArrayBlockingQueue(intcapacity,booleanfair){if(capacity<=0)thrownewIllegalArgumentException();this.items=newObject[capacity];lock=newReentrantLock(fair);notEmpty=lock.newCondition();notFull=lock.newCondition();}/**入队操作*/publicvoidput(Ee)throwsInterruptedException{checkNotNull(e);finalReentrantLocklock=this.lock;lock.lockInterruptibly();try{while(count==items.length)notFull.await();enqueue(e);}finally{lock.unlock();}}/**出队操作*/publicEtake()throwsInterruptedException{finalReentrantLocklock=this.lock;lock.lockInterruptibly();try{while(count==0)notEmpty.await();returndequeue();}finally{lock.unlock();}}}
ArrayBlockingQueue驳回ReentrantLock启动加锁,只要一个ReentrantLock对象,这象征着消费者和消费者不可并行运转。
咱们看一个繁难的示例代码如下:
publicclassContainer{/***初始化阻塞队列*/privatefinalBlockingQueue<Integer>queue=newArrayBlockingQueue<>(10);/***参与数据到阻塞队列*@paramvalue*/publicvoidadd(Integervalue){try{queue.put(value);System.out.println("消费者:"+Thread.currentThread().getName()+",add:"+value);}catch(InterruptedExceptione){e.printStackTrace();}}/***从阻塞队列失掉数据*/publicvoidget(){try{Integervalue=queue.take();System.out.println("消费者:"+Thread.currentThread().getName()+",value:"+value);}catch(InterruptedExceptione){e.printStackTrace();}}}
/***消费者*/publicclassProducerextendsThread{privateContainercontainer;publicProducer(Containercontainer){this.container=container;}@Overridepublicvoidrun(){for(inti=0;i<6;i++){container.add(i);}}}
/***消费者*/publicclassConsumerextendsThread{privateContainercontainer;publicConsumer(Containercontainer){this.container=container;}@Overridepublicvoidrun(){for(inti=0;i<6;i++){container.get();}}}
/***测试类*/publicclassMyThreadTest{publicstaticvoidmain(String[]args){Containercontainer=newContainer();Producerproducer=newProducer(container);Consumerconsumer=newConsumer(container);producer.start();consumer.start();}}
运转结果如下:
消费者:Thread-0,add:0消费者:Thread-0,add:1消费者:Thread-0,add:2消费者:Thread-0,add:3消费者:Thread-0,add:4消费者:Thread-0,add:5消费者:Thread-1,value:0消费者:Thread-1,value:1消费者:Thread-1,value:2消费者:Thread-1,value:3消费者:Thread-1,value:4消费者:Thread-1,value:5
可以很明晰的看到,消费者线程口头终了之后,消费者线程才开局消费。
3.2、LinkedBlockingQueue
LinkedBlockingQueue是一个基于链表的阻塞队列,初始化的时刻毋庸指定队列大小,自动队列长度为Integer.MAX_VALUE,也就是int型最大值。
雷同的,驳回的是ReentrantLock和Condition成功消费者和消费者模型,不同的是它经常使用了两个lock,这象征着消费者和消费者可以并行运转,程序口头效率进一步失掉优化。
局部外围源码如下:
publicclassLinkedBlockingQueue<E>extendsAbstractQueue<E>implementsBlockingQueue<E>,java.io.Serializable{/**经常使用出队独占锁ReetrantLock*/privatefinalReentrantLocktakeLock=newReentrantLock();/**期待出队的条件*/privatefinalConditionnotEmpty=takeLock.newCondition();/**经常使用入队独占锁ReetrantLock*/privatefinalReentrantLockputLock=newReentrantLock();/**期待入队的条件*/privatefinalConditionnotFull=putLock.newCondition();/**入队操作*/publicvoidput(Ee)throwsInterruptedException{if(e==null)thrownewNullPointerException();intc=-1;Node<E>node=newNode<E>(e);finalReentrantLockputLock=this.putLock;finalAtomicIntegercount=this.count;putLock.lockInterruptibly();try{while(count.get()==capacity){notFull.await();}enqueue(node);c=count.getAndIncrement();if(c+1<capacity)notFull.signal();}finally{putLock.unlock();}if(c==0)signalNotEmpty();}/**出队操作*/publicEtake()throwsInterruptedException{Ex;intc=-1;finalAtomicIntegercount=this.count;finalReentrantLocktakeLock=this.takeLock;takeLock.lockInterruptibly();try{while(count.get()==0){notEmpty.await();}x=dequeue();c=count.getAndDecrement();if(c>1)notEmpty.signal();}finally{takeLock.unlock();}if(c==capacity)signalNotFull();returnx;}}
把最上方的样例Container中的阻塞队列成功类换成LinkedBlockingQueue,调整如下:
/***初始化阻塞队列*/privatefinalBlockingQueue<Integer>queue=newLinkedBlockingQueue<>();
再次运转结果如下:
消费者:Thread-0,add:0消费者:Thread-1,value:0消费者:Thread-0,add:1消费者:Thread-1,value:1消费者:Thread-0,add:2消费者:Thread-1,value:2消费者:Thread-0,add:3消费者:Thread-0,add:4消费者:Thread-0,add:5消费者:Thread-1,value:3消费者:Thread-1,value:4消费者:Thread-1,value:5
可以很明晰的看到,消费者线程和消费者线程,交替并行口头。
3.3、SynchronousQueue
SynchronousQueue是一个没有缓冲的队列,消费者发生的数据间接会被消费者失掉并且立刻消费,相当于传统的一个恳求对应一个应对形式。
相比ArrayBlockingQueue和LinkedBlockingQueue,SynchronousQueue成功机制也不同,它关键驳回队列和栈来成功数据的传递,两边不存储任何数据,消费的数据肯定得消费者处置,线程阻塞形式驳回JDK提供的LockSupportpark/unpark函数来成功,也支持公温和非偏心两种形式。
局部外围源码如下:
publicclassSynchronousQueue<E>extendsAbstractQueue<E>implementsBlockingQueue<E>,java.io.Serializable{/**不同的战略成功*/privatetransientvolatileTransferer<E>transferer;/**自动非偏心形式*/publicSynchronousQueue(){this(false);}/**可以选战略,也可以驳回偏心形式*/publicSynchronousQueue(booleanfair){transferer=fair?newTransferQueue<E>():newTransferStack<E>();}/**入队操作*/publicvoidput(Ee)throwsInterruptedException{if(e==null)thrownewNullPointerException();if(transferer.transfer(e,false,0)==null){Thread.interrupted();thrownewInterruptedException();}}/**出队操作*/publicEtake()throwsInterruptedException{Ee=transferer.transfer(null,false,0);if(e!=null)returne;Thread.interrupted();thrownewInterruptedException();}}
雷同的,把最上方的样例Container中的阻塞队列成功类换成SynchronousQueue,代码如下:
publicclassContainer{/***初始化阻塞队列*/privatefinalBlockingQueue<Integer>queue=newSynchronousQueue<>();/***参与数据到阻塞队列*@paramvalue*/publicvoidadd(Integervalue){try{queue.put(value);Thread.sleep(100);System.out.println("消费者:"+Thread.currentThread().getName()+",add:"+value);}catch(InterruptedExceptione){e.printStackTrace();}}/***从阻塞队列失掉数据*/publicvoidget(){try{Integervalue=queue.take();Thread.sleep(200);System.out.println("消费者:"+Thread.currentThread().getName()+",value:"+value);}catch(InterruptedExceptione){e.printStackTrace();}}}
再次运转结果如下:
消费者:Thread-0,add:0消费者:Thread-1,value:0消费者:Thread-0,add:1消费者:Thread-1,value:1消费者:Thread-0,add:2消费者:Thread-1,value:2消费者:Thread-0,add:3消费者:Thread-1,value:3消费者:Thread-0,add:4消费者:Thread-1,value:4消费者:Thread-0,add:5消费者:Thread-1,value:5
可以很明晰的看到,消费者线程和消费者线程,交替串行口头,消费者每投递一条数据,消费者处置一条数据。
3.4、PriorityBlockingQueue
PriorityBlockingQueue是一个基于优先级别的阻塞队列,底层基于数组成功,可以以为是一个无界队列。
PriorityBlockingQueue与ArrayBlockingQueue的成功逻辑,基本相似,也是驳回ReentrantLock来成功加锁的操作。
最大不同点在于:
局部外围源码如下:
publicclassPriorityBlockingQueue<E>extendsAbstractQueue<E>implementsBlockingQueue<E>,java.io.Serializable{/**队列元素*/privatetransientObject[]queue;/**比拟器*/privatetransientComparator<?superE>comparator;/**驳回ReentrantLock启动加锁*/privatefinalReentrantLocklock;/**条件期待与通知*/privatefinalConditionnotEmpty;/**入队操作*/publicbooleanoffer(Ee){if(e==null)thrownewNullPointerException();finalReentrantLocklock=this.lock;lock.lock();intn,cap;Object[]array;while((n=size)>=(cap=(array=queue).length))tryGrow(array,cap);try{Comparator<?superE>cmp=comparator;if(cmp==null)siftUpComparable(n,e,array);elsesiftUpUsingComparator(n,e,array,cmp);size=n+1;notEmpty.signal();}finally{lock.unlock();}returntrue;}/**出队操作*/publicEtake()throwsInterruptedException{finalReentrantLocklock=this.lock;lock.lockInterruptibly();Eresult;try{while((result=dequeue())==null)notEmpty.await();}finally{lock.unlock();}returnresult;}}
雷同的,把最上方的样例Container中的阻塞队列成功类换成PriorityBlockingQueue,调整如下:
/***初始化阻塞队列*/privatefinalBlockingQueue<Integer>queue=newPriorityBlockingQueue<>();
消费者拔出数据的内容,咱们改下拔出顺序。
/***消费者*/publicclassProducerextendsThread{privateContainercontainer;publicProducer(Containercontainer){this.container=container;}@Overridepublicvoidrun(){container.add(5);container.add(3);container.add(1);container.add(2);container.add(0);container.add(4);}}
最后运转结果如下:
消费者:Thread-0,add:5消费者:Thread-0,add:3消费者:Thread-0,add:1消费者:Thread-0,add:2消费者:Thread-0,add:0消费者:Thread-0,add:4消费者:Thread-1,value:0消费者:Thread-1,value:1消费者:Thread-1,value:2消费者:Thread-1,value:3消费者:Thread-1,value:4消费者:Thread-1,value:5
从日志上可以很显著看出,关于整数,自动状况下,依照升序排序,消费者自动从0开局处置。
3.5、DelayQueue
DelayQueue是一个线程安保的提前队列,存入队列的元素不会立刻被消费,只要到了其指定的提后期间,能力够从队列中出队。
底层驳回的是PriorityQueue来存储元素,DelayQueue的特点在于:拔出队列中的数据可以依照自定义的delay期间启动排序,快到期的元素会陈列在前面,只要delay期间小于0的元素能力够被取出。
局部外围源码如下:
publicclassDelayQueue<EextendsDelayed>extendsAbstractQueue<E>implementsBlockingQueue<E>{/**驳回ReentrantLock启动加锁*/privatefinaltransientReentrantLocklock=newReentrantLock();/**驳回PriorityQueue启动存储数据*/privatefinalPriorityQueue<E>q=newPriorityQueue<E>();/**条件期待与通知*/privatefinalConditionavailable=lock.newCondition();/**入队操作*/publicbooleanoffer(Ee){finalReentrantLocklock=this.lock;lock.lock();try{q.offer(e);if(q.peek()==e){leader=null;available.signal();}returntrue;}finally{lock.unlock();}}/**出队操作*/publicEpoll(){finalReentrantLocklock=this.lock;lock.lock();try{Efirst=q.peek();if(first==null||first.getDelay(NANOSECONDS)>0)returnnull;elsereturnq.poll();}finally{lock.unlock();}}}
雷同的,把最上方的样例Container中的阻塞队列成功类换成DelayQueue,代码如下:
publicclassContainer{/***初始化阻塞队列*/privatefinalBlockingQueue<DelayedUser>queue=newDelayQueue<DelayedUser>();/***参与数据到阻塞队列*@paramvalue*/publicvoidadd(DelayedUservalue){try{queue.put(value);System.out.println("消费者:"+Thread.currentThread().getName()+",add:"+value);}catch(InterruptedExceptione){e.printStackTrace();}}/***从阻塞队列失掉数据*/publicvoidget(){try{DelayedUservalue=queue.take();Stringtime=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").format(newDate());System.out.println(time+"消费者:"+Thread.currentThread().getName()+",value:"+value);}catch(InterruptedExceptione){e.printStackTrace();}}}
DelayQueue队列中的元素须要显式成功Delayed接口,定义一个DelayedUser类,代码如下:
publicclassDelayedUserimplementsDelayed{/***以后期间戳*/privatelongstart;/***提后期间(单位:毫秒)*/privatelongdelayedTime;/***称号*/privateStringname;publicDelayedUser(longdelayedTime,Stringname){this.start=System.currentTimeMillis();this.delayedTime=delayedTime;this.name=name;}@OverridepubliclonggetDelay(TimeUnitunit){//失掉以后提前的期间longdiffTime=(start+delayedTime)-System.currentTimeMillis();returnunit.convert(diffTime,TimeUnit.MILLISECONDS);}@OverridepublicintcompareTo(Delayedo){//判别以后对象的提后期间能否大于指标对象的提后期间return(int)(this.getDelay(TimeUnit.MILLISECONDS)-o.getDelay(TimeUnit.MILLISECONDS));}@OverridepublicStringtoString(){return"DelayedUser{"+"delayedTime="+delayedTime+",+name+'''+'}';}}
消费者拔出数据的内容,做如下调整。
/***消费者*/publicclassProducerextendsThread{privateContainercontainer;publicProducer(Containercontainer){this.container=container;}@Overridepublicvoidrun(){for(inti=0;i<6;i++){container.add(newDelayedUser(1000*i,"张三"+i));}}}
最后运转结果如下:
消费者:Thread-0,add:DelayedUser{delayedTime=0,}消费者:Thread-0,add:DelayedUser{delayedTime=1000,}消费者:Thread-0,add:DelayedUser{delayedTime=2000,}消费者:Thread-0,add:DelayedUser{delayedTime=3000,}消费者:Thread-0,add:DelayedUser{delayedTime=4000,}消费者:Thread-0,add:DelayedUser{delayedTime=5000,}2023-11-0314:55:33消费者:Thread-1,value:DelayedUser{delayedTime=0,}2023-11-0314:55:34消费者:Thread-1,value:DelayedUser{delayedTime=1000,}2023-11-0314:55:35消费者:Thread-1,value:DelayedUser{delayedTime=2000,}2023-11-0314:55:36消费者:Thread-1,value:DelayedUser{delayedTime=3000,}2023-11-0314:55:37消费者:Thread-1,value:DelayedUser{delayedTime=4000,}2023-11-0314:55:38消费者:Thread-1,value:DelayedUser{delayedTime=5000,}
可以很明晰的看到,提后期间最低的排在最前面。
四、小结
最后咱们来总结一下BlockingQueue阻塞队列接口,它提供了很多十分丰盛的消费者和消费者模型的编程成功,同时统筹了线程安保和口头效率的特点。
开发者可以经过BlockingQueue阻塞队列接口,繁难的代码编程即可成功多线程中数据高效安保传输的目的,确切的说,它协助开发者减轻了不少的编程难度。
在实践的业务开发中,其中LinkedBlockingQueue经常使用的是最宽泛的,由于它的口头效率最高,在经常使用的时刻,须要平衡好队列长度,防止过大造成内存溢出。
举个最繁难的例子,比如某个配置上线之后,须要做下压力测试,总共须要恳求10000次,驳回100个线程去口头,测试服务能否能反常上班。如何成功呢?
或许有的同窗想到,每个线程口头100次恳求,启动100个线程去口头,可以是可以,就是有点蠢笨。
其实还有另一个方法,就是将10000个恳求对象,存入到阻塞队列中,而后驳回100个线程去消费口头,这种编程模型会更佳灵敏。
详细示例代码如下:
publicstaticvoidmain(String[]args)throwsInterruptedException{//将每个用户访问百度服务的恳求义务,存入阻塞队列中//也可以也驳回多线程写入BlockingQueue<String>queue=newLinkedBlockingQueue<>();for(inti=0;i<10000;i++){queue.put("https://www.baidu.com?paramKey="+i);}//模拟100个线程,口头10000次恳求访问百度finalintthreadNum=100;for(inti=0;i<threadNum;i++){finalintthreadCount=i+1;newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("thread"+threadCount+"start");booleanover=false;while(!over){Stringurl=queue.poll();if(Objects.nonNull(url)){//动员恳求Stringresult=HttpUtils.getUrl(url);System.out.println("thread"+threadCount+"runresult:"+result);}else{//义务完结over=true;System.out.println("thread"+threadCount+"final");}}}}).start();}}
本文关键围绕BlockingQueue阻塞队列接口,从方法引见到用法详解,做了一次性常识总结,假设无形容不对的中央,欢迎留言指出!
五、参考
1.
2.
Linux C编程从初学到精通的目 录
第1部分 基础篇第1章 Linux系统概述 11.1 什么是Linux 21.2 Linux系统特点及主要功能 21.2.1 Linux系统特点 31.2.2 Linux系统的主要功能 31.3 Linux的内核版本和发行版本 51.4 系统的安装 61.4.1 系统安装前的准备工作 61.4.2 从光盘安装Linux 61.4.3 从硬盘安装Linux 221.4.4 在虚拟机下安装Linux 221.5 Shell的使用 271.5.1 Shell简介 271.5.2 常见Shell的种类 281.5.3 Shell的简单使用 291.5.4 通配符 301.5.5 引号 311.5.6 注释符 331.6 Linux常用命令 331.6.1 与目录相关的命令 331.6.2 与文件相关的命令 341.6.3 与网络服务相关的命令 351.7 本章小结 35实战演练 36第2章 C语言编程基础 372.1 C语言的历史背景 382.2 C语言的特点 382.3 C语言的基本数据类型 392.3.1 整型 392.3.2 实型 402.3.3 字符型 412.4 运算符与表达式 432.4.1 算术运算符与算术表达式 432.4.2 赋值运算符与赋值表达式 442.4.3 逗号运算符与逗号表达式 452.5 C程序的3种基本结构 462.5.1 顺序结构 462.5.2 选择结构 472.5.3 循环结构 512.6 C语言中的数据输入与输出 542.6.1 字符输出函数putchar 542.6.2 字符输入函数getchar 542.6.3 格式输出函数printf 542.6.4 格式输入函数scanf 562.7 函数 572.7.1 函数的定义 572.7.2 函数的调用 582.7.3 变量的存储类别 592.8 数组 622.8.1 一维数组的定义和使用 632.8.2 二维数组的定义和使用 642.8.3 字符数组和字符串 652.8.4 常用字符串处理函数 662.9 指针 692.9.1 地址和指针 692.9.2 指针的定义和使用 702.9.3 数组与指针 712.9.4 字符串与指针 722.9.5 指向函数的指针 722.10 结构体和共用体 732.10.1 定义和引用结构体 732.10.2 结构体数组 742.10.3 指向结构体的指针 742.10.4 共用体 752.10.5 使用typedef定义类型 772.11 链表 772.11.1 链表概述 772.11.2 建立动态单向链表 782.11.3 单向链表的输出 802.11.4 对单向链表的删除操作 802.11.5 对单向链表的插入操作 812.11.6 循环链表 822.11.7 双向链表 822.12 位运算符和位运算 832.12.1 “按位与”运算符(&) 842.12.2 “按位或”运算符(|) 842.12.3 “取反”运算符(~) 842.12.4 “异或”运算符(^) 842.12.5 移位运算符(<<和>>) 852.12.6 位域 852.13 C语言预处理命令 862.13.1 宏定义 862.13.2 文件包含 872.13.3 条件编译 882.13.4 #error等其他常用预处理命令 892.14 本章小结 89实战演练 89第3章 vi与Emacs编辑器 913.1 vi的使用 923.1.1 启动与退出vi 923.1.2 vi的命令行模式 933.1.3 vi的插入模式 963.1.4 vi的底行模式 963.2 vi使用实例 973.3 Emacs的使用 1003.3.1 启动与退出Emacs 1013.3.2 Emacs下的基本操作 1023.4 Emacs使用实例 1073.5 本章小结 109实战演练 109第4章 gcc编译器与gdb调试器 1104.1 gcc编译器简介 1114.2 如何使用gcc 1124.2.1 gcc编译初步 1124.2.2 警告提示功能 1144.2.3 优化gcc 1164.2.4 连接库 1194.2.5 同时编译多个源程序 1204.2.6 管道 1204.2.7 调试选项 1214.3 gdb调试器 1224.3.1 gdb简介 1224.3.2 gdb常用命令 1234.3.3 gdb调试初步 1244.4 gdb的使用详解 1264.4.1 调用gdb 1274.4.2 使用断点 1274.4.3 查看运行时数据 1294.4.4 查看源程序 1334.4.5 改变程序的执行 1354.5 xxgdb调试器简介 1384.6 本章小结 139实战演练 139第5章 make的使用和Makefile的编写 1415.1 什么是make 1425.1.1 make机制概述 1425.1.2 make与Makefile的关系 1445.2 Makefile的书写规则 1475.2.1 Makefile的基本语法规则 1485.2.2 在规则中使用通配符 1495.2.3 伪目标 1495.2.4 多目标 1515.2.5 自动生成依赖性 1515.3 Makefile的命令 1525.4 变量 1545.4.1 变量的基础 1545.4.2 赋值变量 1545.4.3 define关键字 1565.4.4 override指示符 1565.4.5 目标变量和模式变量 1575.5 常用函数调用 1585.5.1 字符串处理函数 1585.5.2 文件名操作函数 1625.5.3 循环函数 1645.5.4 条件判断函数 1655.5.5 其他常用函数 1665.6 隐式规则 1685.6.1 隐式规则举例 1685.6.2 隐式规则中的变量 1695.6.3 使用模式规则 1705.7 本章小结 173实战演练 173第2部分 提高篇第6章 文件I/O操作 1746.1 Linux文件系统简介 1756.1.1 Linux的文件系统结构 1756.1.2 文件类型 1766.1.3 文件访问权限 1796.2 基于文件描述符的I/O操作 1796.2.1 文件描述符 1806.2.2 标准输入、标准输出和标准出错 1806.2.3 文件重定向 1816.2.4 文件的创建、打开与关闭 1826.2.5 文件的定位 1866.2.6 文件的读写 1886.3 文件的属性操作 1926.3.1 改变文件访问权限 1926.3.2 改变文件所有者 1936.3.3 重命名 1936.3.4 修改文件长度 1946.4 文件的其他操作 1956.4.1 stat、fstat和lstat函数 1956.4.2 dup和dup2函数 1966.4.3 fcntl函数 1976.4.4 sync和fsync函数 1976.5 特殊文件的操作 1986.5.1 目录文件的操作 1986.5.2 链接文件的操作 2016.5.3 管道文件的操作 2046.5.4 设备文件 2046.6 本章小结 205实战演练 205第7章 基于流的I/O操作 2067.1 流与缓存 2077.1.1 流和FILE对象 2077.1.2 标准输入、标准输出和标准出错 2077.1.3 缓存 2077.1.4 对缓存的操作 2107.2 流的打开与关闭 2127.2.1 流的打开 2127.2.2 流的关闭 2147.2.3 流关闭前的工作 2167.3 流的读写 2167.3.1 基于字符的I/O 2177.3.2 基于行的I/O 2207.3.3 直接I/O 2227.3.4 格式化I/O 2247.4 本章小结 226实战演练 227第8章 进程控制 2288.1 进程的基本概念 2298.1.1 Linux进程简介 2298.1.2 进程与作业 2308.1.3 进程标识 2308.2 进程控制的相关函数 2328.2.1 fork和vfork函数 2328.2.2 exec函数 2378.2.3 exit和_exit函数 2428.2.4 wait和waitpid函数 2458.2.5 进程的一生 2518.2.6 用户ID和组ID 2518.2.7 system函数 2538.3 多个进程间的关系 2558.3.1 进程组 2558.3.2 会话期 2568.3.3 控制终端 2578.4 本章小结 259实战演练 259第9章 信号 2609.1 Linux信号简介 2619.1.1 信号的基本概念 2619.1.2 信号处理机制 2659.2 信号操作的相关函数 2679.2.1 信号的处理 2679.2.2 信号的发送 2749.2.3 信号的阻塞 2829.2.4 计时器与信号 2849.3 本章小结 286实战演练 287第10章 进程间通信 .1 进程间通信简介 .2 管道 .2.1 管道的概念 .2.2 管道的创建与关闭 .2.3 管道的读写 .3 命名管道 .3.1 命名管道的概念 .3.2 命名管道的创建 .3.3 命名管道的读写 .4 消息队列 .4.1 消息队列的概念 .4.2 消息队列的创建与打开 .4.3 消息队列的读写 .4.4 获得或设置消息队列属性 .5 共享内存 .5.1 共享内存的概念 .5.2 共享内存的相关操作 .6 信号量 .6.1 信号量的概念 .6.2 信号量集的相关操作 .7 本章小结 325实战演练 326第11章 网络编程 .1 网络编程的基础知识 .1.1 计算机网络体系结构 .1.2 传输控制协议TCP .1.3 用户数据报协议UDP .1.4 客户机/服务器模式 .2 套接口编程基础 .2.1 什么是套接口 .2.2 端口号的概念 .2.3 套接口的数据结构 .2.4 基本函数 .3 TCP套接口编程 .3.1 TCP套接口通信工作流程 .3.2 TCP套接口Client/Server程序实例 .4 UDP套接口编程 .4.1 UDP套接口通信工作流程 .4.2 UDP套接口Client/Server程序实例 .5 原始套接口编程 .5.1 原始套接口的创建 .5.2 原始套接口程序实例 .6 本章小结 376实战演练 376第12章 Linux图形界面编程 .1 Linux下的图形界面编程简介 .1.1 Qt简介 .1.2 GTK+简介 .2 界面基本元件 .2.1 一个简单的例子 .2.2 窗口 .2.3 标签 .2.4 按钮 .2.5 文本框 .3 界面布局元件 .3.1 表格 .3.2 框 .3.3 窗格 .4 其他常用元件 .4.1 进度条、微调按钮、组合框 .4.2 单选按钮、复选按钮 .4.3 下拉菜单 .5 信号与回调函数 .6 本章小结 409实战演练 409第3部分 实战篇第13章 设计Linux下的计算器 .1 软件功能分析 .2 程序模块的划分 .3 软件的具体实现 .3.1 头文件 .3.2 十六进制界面显示函数 .3.3 十进制界面显示函数 .3.4 八进制界面显示函数 .3.5 二进制界面显示函数 .3.6 进制间转换函数 .3.7 信号处理模块 .3.8 主函数 .4 软件使用效果展示 .5 本章小结 439第14章 Linux平台下聊天软件的设计 .1 软件功能概述 .1.1 服务器端功能需求 .1.2 客户端功能需求 .1.3 错误处理需求 .2 Glade集成开发工具简介 .3 软件功能模块划分 .3.1 服务器功能模块划分 .3.2 客户端功能模块划分 .3.3 消息标识的定义 .3.4 消息结构体的设计 .4 服务器程序的具体实现 .4.1 服务器消息处理流程 .4.2 服务器主要函数和变量 .4.3 服务器消息处理模块的设计与实现 .4.4 服务器数据存储的方法 .4.5 用户注册流程 .5 客户端程序的具体实现 .5.1 客户端操作流程 .5.2 客户端发送和接收消息流程 .5.3 客户端主要函数和变量 .5.4 客户端功能模块的设计与实现 .6 聊天软件使用效果展示 .7 本章小结 459第15章 Linux远程管理工具的设计 .1 软件功能概述 .1.1 Webmin简介 .1.2 软件总体设计 .2 服务器端程序设计 .2.1 服务器端工作流程 .2.2 系统用户管理操作 .2.3 系统用户组的管理操作 .2.4 系统服务启动管理 .2.5 DNS管理操作 .2.6 Apache服务管理操作 .2.7 FTP服务管理操作 .3 客户端程序 .3.1 连接界面 .3.2 主界面 .4 本章小结 479第16章 Linux下简易防火墙软件的设计 .1 Netfilter基础 .1.1 什么是Netfilter .1.2 Netfilter的HOOK机制 .1.3 HOOK的调用 .1.4 HOOK的实现 .1.5 IPTables简介 .1.6 Netfilter可以实现的控制功能 .2 软件设计概述 .2.1 软件整体框架 .2.2 管理端的设计 .2.3 控制端的设计 .3 用Netfilter设计控制端功能模块 .3.1 ICMP管理控制模块 .3.2 FTP管理控制模块 .3.3 HTTP管理控制模块 .3.4 模块的编译、加载与卸载 .4 软件功能测试 .5 本章小结 503第17章 基于Linux的嵌入式家庭网关远程交互操作平台的设计 .1 嵌入式技术简介 .1.1 嵌入式系统的概念 .1.2 嵌入式操作系统 .1.3 嵌入式处理器 .2 家庭网关的概念及其网络体系结构 .2.1 智能家庭网络的概念 .2.2 家庭网关的远程交互操作技术简介 .2.3 嵌入式家庭网关的网络体系结构 .3 嵌入式家庭网关的开发平台 .3.1 S3C2410微处理器简介 .3.2 交叉编译环境的建立 .4 远程交互平台的设计 .4.1 应用软件的开发模式 .4.2 嵌入式Web服务器 .4.3 通用网关接口CGI .5 Linux下软件模块的具体实现 .5.1 登录验证模块 .5.2 串口通信模块 .5.3 中央空调控制模块 .5.4 智能水表数据采集模块 .5.5 试验结果 .6 本章小结 529
java该怎么自学?
自学的困难就是,不知道该从哪里开始,才怎么学,没有一个系统的学习路径,现在黑马程序员最新上线了java学习路线图,非常好的解决了一个难题,可以去搜索看一下。
一、java基础
学习任何一门编程语言,首先要学习的是基础语法,开启Java学习的第一步,当然就是深入掌握计算机基础、编程基础语法,面向对象,集合、IO流、线程、并发、异常及网络编程,这些我们称之为JavaSE基础。当你掌握了这些内容之后,你就可以做出诸如:电脑上安装的迅雷下载软件、QQ聊天客户端、考勤管理系统等桌面端软件。
JavaSE基础是Java中级程序员的起点,是帮助你从小白到懂得编程的必经之路。
在Java基础板块中有6个子模块的学习:
技术树
二、数据库
互联网最具价值的是数据,任何编程语言都需要解决数据存储问题,而数据存储的关键技术是数据库。MySQL和Oracle都是广受企业欢迎的数据库管理系统。Java程序和数据库通信的最常见技术是JDBC,Druid和C3P0。学习这些数据库技术后,可以掌握数据库运维技术、复杂业务表结构设计规范、工作中常见的SQL操作、软件数据存储等。
数据库不仅仅是Java开发工程师的必学课程,也是其他语言都需要掌握的技能。用于对交互过程中客户的数据进行存储。
该板块包括关系型数据库和非关系型数据库。
例如:MySQL、oracle、redis、MongoDB等。数据库学习完毕后,可以将数据存储到数据库中,也可以通过SQL语句从数据库中查询数据,结合Java项目可以实现动态站点的数据的保存。
技术树
三、前端技术
浏览器展示给用户看到的网页就是前端,前端有三大基础技术分别为Html、CSS、JavaScript,这些学完后,为了做出更好、更炫的交互式体验效果,我们还需要学习jQuery、ElementUI、Vue、Ajax,以及打包工具webpack。学完这些技术后,我们可以开发微信小程序、响应式网站、移动端网站、开发类似京东一样的B2B2C商城、管理后台等。
Javaweb阶段包括前端、数据库和动态网页。Javaweb是互联网项目的入门课程,是学习后面高进阶课程的基础。
首先,我们先看一下前端板块。该板块主要包括如下几个模块:
学习前端技术后,可以完成类似京东、淘宝的前端工程的编写。
技术树
四、动态网页
掌握前端技术只能做静态网站,但它页面数据一成不变,而动态网站可以根据数据库中变更的数据实现不同的内容展示,应用更广泛,因此程序员必须要学会做动态网站。使用Java做动态网站,我们需要学习Servlet、Filter、Session、Cookie、JSP、EL表达式、JSTL等做动态网站的完整知识体系,学完可研发出OA系统、内容网站、BBS等。
动态网页是中级程序员服务器端编程的基础,是高级框架学习的必备课程,后期学习的框架、服务底层都是基于动态网页技术之上的。
该板块包括Javaweb核心技术、包括Servlet、Request、Response、Cookie和Session等,通过这些技术的学习可以完成动态站点开发,可更好的完成服务器端与客户的交互,让页面的数据“动”起来,做出小型的应用系统。
技术树
五、编程强化
前面学了JavaSE基础,但它在企业级应用中程序处理业务的效率并不高、扩展差,编程强化是对JavaSE基础的加强,将针对性的提高程序处理业务的执行效率、增强程序扩展性。编程强化将加强多线程高级学习,涉及线程内存、线程通信等技术。学完以后,能增加一个中级程序员的知识储备,无论在面试过程中还是将来技术的深入打一个良好的基础。
编程强化是对解决实际问题方面做一个深入的了解和应用,是对JavaSE基础的加强,对后期自动以框架和对一些服务框架的底层理解做支撑。
编程强化板块主要包括如下几个模块:多线程高级、涉及线程内存、线程通信等;JVM优化,对JVM底层进行调优来提高项目执行效率;NIO,同步非阻塞IO来提高效率。
学习该阶段,可以对原有项目进行优化从而使程序更快更稳定。
技术树
六、软件项目管理
公司开发都是团队协同开发,为更好的掌握实际开发,我们还需要学习常用的项目管理平台、版本控制器、项目构建工具以及自动化部署工具。项目开发一定是有版本升级的,管理好项目进度和版本需要Git、Maven、Sonar这样的系统平台。学习完软件项目管理后,将掌握整个项目实际开发过程以及整个项目开发过程中所使用协同开发工具。
JavaSE基础是Java中级程序员的起点,是帮助你从小白到懂得编程的必经之路。
在Java基础板块中有6个子模块的学习:基础语法,可帮助你建立基本的编程逻辑思维;面向对象,以对象方式去编写优美的Java程序;集合,后期开发中存储数据必备技术;IO,对磁盘文件进行读取和写入基础操作;多线程与并发,提高程序效率;异常,编写代码逻辑更加健全;网络编程,应用服务器学习基础,完成数据的远程传输。
学习该阶段,可以完成一些简单的管理系统、坦克大战游戏、QQ通信等。
技术树
七、热门技术框架
Javaweb掌握后,已经具备企业中实际项目的开发能力了,但它开发效率低,代码量大,开发周期长、开发成本高。企业中广泛使用一些优秀的框架技术来解决上述问题,因此我们还需要学习框架技术,项目开发中主流的Java框架技术有SpringMVC、Spring、MyBatis、MyBatis Plus、SpringData等。这些框架技术都是一个优秀程序员所必备的技能。
使用Javaweb进行企业级开发是完全可以的,但是开发效率比较低,所以对常用的逻辑操作进行封装就形成了框架,因此框架是企业开发的入门技能。
![一文带你彻底把握阻塞队列](https://tyye.cn/zb_users/theme/tpure/style/images/lazyload.png)
热门框架板块主流框架有如下几个:Spring框架,占据统治地位,其生态系统涉及各个方面解决方案;MyBatis框架,使用ORM思想对数据库进行操作。
该板块学习后,就可以进行真实企业级项目开发了,做出的项目也会更加符合企业要求。
技术树
八、分布式架构
需要用到分布式微服务的技术。学习完该阶段课程,可以具备大型SOA架构和微服务架构能力,能掌握大型微服务项目必备技术和实际经验。企业发展过程中,业务量和用户量逐渐增加,为了保证系统的可用性,系统越做越复杂,研发人员增多,大家很难共同维护一个复杂的系统,往往修改部分内容,导致牵一发而动全身,所以我们需要升级系统架构,
随着互联网的发展,业务的复杂性和用户的体验性都需要提高,所以分布式架构出现了。该板块主要讲解的是分布式架构的相关解决方案。
主要包括如下模块:Dubbo,高性能的 RPC 服务发布和调用框架;SpringBoot,简化Spring应用的初始搭建以及开发过程;Spring Cloud,一系列框架的有序集合,如服务发现注册、配置中心、负载均衡、断路器、数据监控等。
该板块的学习,可以具备大型互联网项目开发的必备技术和实际经验,为进入BATJ打下基础
技术树
九、服务器中间件
在分布式系统架构中,服务与服务之间的异步通信,是非常常见的需求之一,消息中间件的诞生正是为了解决这类问题。目前市面上的主流消息中间件有RabbitMQ、RocketMQ、Kafka,我们将学习这3个消息中间件,实现分布式项目中的异步通信。学习完这些后,可以实现分布式项目的异步通信、分布式应用日志收集、分布式事务等。
中间件板块是大型互联网项目中必备的。服务中间件可以帮助各子模块间实现互相访问,消息共享或统一访问等功能。其包括远程服务框架中间件,例如阿里(Apache)的RPC框架Dubbo等;消息队列中间件,例如:阿里巴巴开源分布式中间件RocketMQ、高吞吐量消息发布和流处理服务Kafka等。
学习服务中间件是中级JavaEE工程师必要技术,也是JavaEE架构师必须精通的技术。
技术树
十、服务器技术
程序开发完成后,我们把它们打包部署到服务器中运行,所以我们需要学习常见的服务器技术,常见的服务器有Linux和Window server,Linux性能高,是当前主流。我们写好的项目需要用一个软件运行起来,这个软件叫web容器,我们需要在服务器上安装web容器来发布项目,当前主流的web容器有tomcat、jetty、nginx、undertow。
不管是使用原生Javaweb进行开发,还是使用框架进行开发,项目最终需要对外发布才能供全世界的人访问到,而服务器板块就可以解决这个问题,所以服务器是项目发布的必要技术。该板块包括虚拟化和web应用服务器的学习,主要包括如下几个模块:Vmware,虚拟机软件;Linux,专门用于服务器的系统;Nginx,集群部署时反向代理服务器;Tomcat,项目发布时主要使用的服务器。
该板块学习后,我们就可以把开发好的项目发布到服务器中,然后供你的小伙伴远程访问了,超酷!
技术树
十一、容器技术
具备了服务器操作系统及web容器,我们就可以部署单机的站点,在分布式系统中,几十上百的服务,如果使用单机这种部署方式,会投入很高的人力,同时出错的几率也大。所以服务器虚拟化技术Docker也称为如今的必备技术了,Docker可以帮助运维人员实行快速部署,批量维护.使用Kubernetes实现自动化部署、大规模可伸缩、应用容器管理。
容器化技术是近两年超级火的一个专题,通过容器化技术可以对环境进行打包,方便移植,大大提高了开发效率。该板块包括容器化技术Docker和其平台管理引擎Kubernetes,其中,Docker 是一个开源的应用容器引擎,可以打包应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的Linux或Windows 机器上,也可以实现虚拟化。而Kubernetes是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效。通过该板块的学习,你可以通过上述技术快速搭建环境,节省开发时间,提高开发效率。
技术树
十二、业务解决方案
企业开发中会遇到一些通用的业务场景,诸如:搜索引擎、缓存、定时任务、工作流、报表导出、日志管理、系统监控等,那么这些通用的解决方案也有现成优秀的免费开源中间件,可供使用。诸如:ElasticSearch、Lucene、Solr、redis、MongoDB、slf4J、ECharts、Quartz、POI等。业务解决方案课程的业务方案和技术难点,解决了企业开发中90%以上的痛点和难点。
虽然我们已经具备了基础技术和高阶技术,但是要想与企业开发相接轨,还需要对实际项目的业务解决方案进行探究。而此版块就是在实际业务场景中的真实解决方案集合,常用的业务解决方案有如下:搜索业务场景解决方案、日志收集与分析场景解决方案、工作流引擎场景解决方案、任务调度场景解决方案、地图开发平台场景解决方案、支付开放平台场景解决方案、图表可视化场景解决方案。通过分析实际业务来学习这个解决方案技术集,完全可以达到中级甚至高级工程师水平。
技术树
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。