首先我们先介绍一些基本概念,我们为什么要使用并行,并行的大环境是什么样子的,以及和并行相关的一些概念,最后我们会介绍两个和性能,两个相关的定律,要有一定的JAVA使用经验,我们首先来探讨一个非常重要的问题,为什么我们要写并行的代码,并行代码之所有有这个需求呢,主要是基于两个原因,第一个是业务的要求,比方说,我们有一个服务器,他要处理多个客户端的请求,那么一种比较通俗的做法呢,每个客户端使用一个线程去做,当然我们也可以使用一个线程去处理多个客户端请求,但是这样我们可能要处理一些客户端之间的一个调度问题,那这样对我们的代码实现呢,就会有一些复杂的地方,比如说我们的java虚拟机,当我们JAVA虚拟机起来之后呢,JAVA虚拟机在后面运行了好多的线程,其中有一个是启动我们main函数的主线程,这些线程共同维护了虚拟机的运作,之所以使用这么多线程的原因呢,是因为我们业务模块上需要,比如及时编译,他就是为了编译而存在的,GC线程就是为了做GC而存在的,我们主函数main线程就是为了运行我们的代码而存在的,如果没有线程这个概念,我们不使用并行,我们只能在一个串行的程序当中,同时去实现一些功能,比较自然的做交替的,并不是一个执行完了,执行另外一个,而是会有交叉,如果我们要完成自己来处理,那么会是非常困难的,我们会涉及到很多的任务调度等等,那么使用线程呢,他就可以很好地,解决这个问题,调度的问题呢,交给操作系统去做,对我们应用开发来讲呢,只需要简单的处理一些,业务上的模块就行了,因此使用并行,使用多线程,一个重要的原因呢,业务上需要一个执行单元,所以为了有这么一个执行单元呢,我们很自然的想到了线程这个问题,不用进程,进程太大,你一个进程创建和销毁的开销呢,比线程大的多,所以相对来讲呢,所以我们就选择更低消费的一个实体,这个是一个重要的原因,我们主要探讨的是第二个原因,就是性能,使用多线程的程序呢,多核CPU的性能呢,一般是要比单线程的性能要好一些,但是对于并行程序,他是好是坏,是利大于弊,其实还是有一些探讨的,比如说,大家看这个就是我们的大神

它是linux的创建人,它基于linux系统做了一些修改,来创建linux,所以像这种大神级的任务,他提出了一个什么意见呢,忘掉那该死的并行,他有这么一句话,有多么奇葩的想象力,才能想象并行计算的用武之地,言下之意呢,是说这个并行计算其实就没有用,其中一个重要的原因呢,并行的代码要比串行的代码要多得多,并行的代码是很难维护的,所以对并行也提出质疑,这个东西根本就没有多大用处,那么事实上是不是这样子呢,我罗在这里的技术都讨论过,不排除媒体对他断章取义,因为批评MINIX也道歉了,那么JAVA他也批判过,看到的是JAVA引擎在走下坡路,因为他别无去处,98年的时候说了这句话,2011年的时候说过,我不关心JAVA,多么可怕的语言,事实上同样的话用在了C++上面,他说C++是门可怕的语言,他在做git的时候也是用C,并没有选中C++,包括XML,和Solaris,他都批判过,Solaris很简单,Solaris是linux的竞争对手,他希望他死掉,那么这个一个对技术不断批判的一个人物,他在说并行不行的时候呢

并行计算只有在图像处理和服务端编程可以用,并且他在这两个领域确实有着大量的使用,但是在其他地方,并行计算毫无建树,事实上他说这句话是很有道理的,因为图像处理,它是属于计算密集型的,这个领域,你单个CPU计算可能会有点累,当你需要去处理大量的计算的时候呢,能够明显提高系统的性能,第二个领域就是服务端编程,比如服务器,后台在运作的一个程序,因为这些程序他往往也需要大量的处理,往往也是计算密集型的,比如你做一个数据挖掘,做一些数据分析,他也有可能像不同的服务器去请求数据,因此呢他在这两个领域呢,JAVA语言的主要工作场景呢,也就是在服务端进行,因此呢从这个角度来看,为什么要使用并行呢,是因为业务上的原因,第二个呢是并行程序在多核CPU上,确实可以提高性能,在现在这个大环境下来说,JAVA应用到服务端编程,与并行主要执行领域是一致的,用于用户UI的操作,并行程序,并行多线程,其实对于用户的价值并不是很大

并行程序流行开来的重要原因呢,多核CPU的产生,为什么会有多核CPU呢,那是因为摩尔定律在单核CPU发展已经失效了,根据摩尔定律的说法呢,每18个月到24个月,就是一年半到两年的时间,我们芯片的性能是会提高一倍的,翻一翻,但事实上是什么情况呢,在2004年因特尔就宣布彻底取消了4GHZ的计划,在他当时的基础上面,他要做一个主频是4GHZ的芯片呢,他做不出来,现在已经是2015年了,现在能够买的主频呢,基本上也就是4GHZ多一点点,是到顶了,绝大部分还是留在3.8G,4G以下,4GZ芯片是很少的,而且卖得很贵,而且再往上发展的可能性呢,看起来也不大,从这里可以看到,10年时间过去了,CPU的主频,基本上已经没有太大的变化,那么按照摩尔定律,我们如果按24个月算的话,那你10年过去了,应该翻了5翻,那也应该是32倍,如果那个时候能够达到3G,现在CPU的主频,按照摩尔定律的说法呢,达到100G也没问题,实际上我们离100G还有很远,因此计算机的发展呢,实际上在这个地方已经受到了一个瓶颈,所以摩尔定律在芯片的发展呢,已经失效了,如果计算没法提高性能,那我们应该怎么办呢,人们就很聪明了,既然你没有办法提高你单个CPU的性能,那我们就在一个CPU里面呢,我塞很多的核进去,因此多核CPU就兴起了,单核CPU的瓶颈之后,导致我们往多核CPU发展,这个并不是我们的选择,而是一条没有选择的道路,因为我们做不出来了,预计将来68核,他也会产生,说不定现在也有,后端也有64核CPU的使用了,如果将来的CPU内核越来越多,那么很显然并行的需求也会越来越大

那我们再来看唐纳德他所说的一句话,这也是计算机中的一个顶级科学家,他说在我看来,这种现象,并发,或多或少是因为硬件设计者已经无计可施了,他们将摩尔定律的责任呢,推脱给了软件开发者,也就是说,因为我没有办法提高CPU的主频,所以那你就去用多个CPU,来做这件事情,换个角度来说,如果我们现在有10G,100G的CPU,其实并行不像现在这么重要,因为我们完全可以写在一个串行的程序当中,他反正是运行的很快,我们也可以降低编程难度,在多核CPU上的可见性,这样的问题产生了,但是事实上我们没有,我们没有这样的CPU,所以我们不得不去做并行这件事情

这是因为,我们刚才说过的,需要并行是因为业务模型的需要

下面来看一下并行的几个比较重要的概念,这里会介绍同步,异步,并发,并行,临界区,阻塞和非阻塞,饥饿,活锁,并行级别的概念

首先我们来看一下同步和异步,同步异步是对方法调用而言的,如果一个方法调用是同步的,那在这个时间轴上,我们可以看到,同步调用呢,它会等待这个方法返回,这个方法实现多久,他就等待多久,异步调用呢它会瞬间返回,异步他返回很快,异步调用返回很快,并不表示说你,这个调用就完成了,它会在后台启一个线程,一般都是启一个线程,慢慢地做他的事情,所以异步调用呢,是指我一个函数调用下去,我马上就能够返回,但是我返回之后呢,这个程序我调用的请求呢并没有做完,但是我不影响我继续做我下面的事情,在我异步调用之后,还能够做我下面的其他的事情,我异步调用的工作内容,他在另外一个线程当中,慢慢去做,那这就是异步调用和同步调用,异步调用和同步调用的含义

下面我们来看一下并发和并行的区别,一般来说呢,不需要特别区分并发和并行,他们有什么特别重要的区别,因为对我们外界看来呢,并发和并行他的外在表象,基本上是一致的,并发他的英文单词,Concurrency,并行叫做Parallelism,这样的程序叫做并行,两个线程,或者两个进程也好,他们同时在里面执行,这叫并行,什么叫并发呢,并发是指我一会做这个事情,一会做这件,他有一个调度的过程,这个叫并发,对于单个CPU来讲,他不可能出现并行的情况,单个CPU他同一个时间只能做同一件事情,它是分时的在做调度,调度这所有的任务,但是对于多个CPU来讲呢,他就是一个并行的,两个CPU,每个CPU做一件事情,这两个程序就是并行执行,但对于外在表象而言,我们看到这两件任务都是同时执行的,所以一般来说呢,我们不用特别去关注,但是在有些场合呢,我们还是需要去确认一下,在 一般来说,这两个概念对于我们来说呢,是一样的

下面我们来看一下临界区,对于多线程程序来讲呢,临界区是一个非常重要的概念,我们提到临界区呢,就表示他是一个公共的区域,也就是说,所有的线程,他都能够访问这个公共的区域,所有的线程都能够访问他,因为所有的线程都能够访问他,当然多个线程去访问这个临界区呢,那可能会破坏掉,比如你一个人去写数据还没有写完,另外一个线程又去里面去写数据,那么着两个数据一叠加,就可能会产生错误的数据,因此呢临界区呢,它是一个需要被控制的区域,我希望多个线程进入临界区,有可能把我数据改坏掉,所以每一次我只希望有一个线程可以进去,那当线程进去之后呢,其他线程还想进入到临界区里面来呢,需要进入阻塞队列进行等待,等到什么时候为止呢,等待我临界区里的线程,释放了这个锁,其他可以在等待队列当中呢,再取一个线程,这里讲的是临界区的控制方法,临界区本身就是有共享资源,当多线程访问的时候呢,就关注他额外不会被破坏的数据

下面我们来看一下阻塞和非阻塞,阻塞和非阻塞通常用来形容多线程间的相互影响,如果一个线程占用了临界区,其他线程不能够再进去,就是阻塞,因为其他线程要在临界区之外做等待,阻塞的含义呢是指线程操作系统层面,被挂起,所以阻塞的方式呢,他一般性能不会太好,根据一般的统计呢,如果你一个线程在操作层面,把你调出去了,做了上下文切换了,所以这不是一个特别好的方法,但是是一个非常简单的方法,所有责任都推给了操作系统,去帮我们调度,虽然说他的性能不高,能把这件事情做得很好,阻塞它会有一个问题,如果说我有个线程,一直占着不释放资源,其他要使用临界区的资源呢,都会不能工作,都会阻在那边,那么非阻塞是指允许很多线程同时进入临界区,那这个就是非阻塞,我一个线程进入临界区之后,我该线程也能够进去,我只要保证不把数据改坏,就可以了

下面我们再来看一下死锁,节饥饿和活锁,对于阻塞的程序来讲,如果是进入临界区,就有可能发生死锁的现象,比如这四辆小车,就发生了死锁,每辆小车所占的一条路呢,我们认为是一个资源,A车把这条路给堵了,B车把这条路给堵了,C车把这条路堵了,D车把这条路堵了,但是A需要这条路,C需要这条路,D需要这条路,B需要这条路,这样就导致ABCD,没有人能够继续往下走,除非我们把其中一个给停止掉,就是把D车挪走,C车就能走,B车也能走,A车也能走,这就是死锁,死锁就是使得整个程序卡死在里边,不再提供服务了,如果我们系统出现了这个问题呢,死锁虽然说不是一个好现象,但是死锁它是一个静态的一个问题,也就是说一旦发生死锁呢,他不会占用CPU,所以这是一个静态的问题,相对来讲还是比较好分析的,与死锁相对应的还有一个活锁,活锁是什么意思呢,我们举个例子,比方说,我们电梯门开了,电梯门开了之后呢,里面的人要出来,假设电梯里有一个人,电梯外也有一个人,电梯里面的那个人呢,要出来,电梯外面那个人要进去,那有可能碰到的问题是说,你面对面撞在了一起,你们两个人把路都堵了,那你可能要避让他,所以你就往左边考了一靠,想避开他,他的想法是跟你一样的了,他也避开你,他就往右边靠了一靠避开你,因为你们两个是面对面的,所以你往左边靠,他往右边靠的时候呢,你们两个又把路给堵上了,然而你这个时候发现不对,所以你又往右边靠,他发现不对,他呢再往左边靠,然后你们又把路给堵上了,与此往复,不停的两个人,一会往左一会往右,不停的把这个路给堵上,作为人来讲,你发现这种情况呢,我们会停下来,眼神交流一下,或者我们停下来不动,让另外一个人先过,这个问题又解决了,因为人是有智力的,但是如果这个情况发生在程序上,发生在线程上面呢,你就不会这么走运了,一个线程如果他抢占到资源之后,他发现另外的资源没有办法拿到,这个时候他为了避免死锁,如果死锁产生的一个必要条件,你抢占了资源而不释放,如果你抢占了资源而释放了呢,就没有死锁这种说法,因为他没有办法拿到所有的资源,因此他把自己释放的那块资源也给释放掉,而这个时候另外一个线程,做了同样的事情,他们需要相同的资源,比如他们都需要资源A,一个线程抢了资源A,另外一个资源抢了资源B,然而他们发现都没法工作,把资源同时释放掉,释放了之后呢,他们一看,资源A和资源B都出来了,然后一个线程拿了资源B,另外一个线程拿了资源A,然后一看,我又没法工作了,然后他们又互相谦让,又把这个资源放出去,与此反复,这个资源在多个线程之间跳来跳去,活锁一旦发生,这个问题就比死锁更难查,因为它是一个动态的一个问题,并不是像死锁那样,现在就进在里面不动了,线程不停的在动,不停的在重试,但是永远不能够成功,或者说要花很长的时间才能够成功,性能也会受到很大的影响,那就是活锁,那么有关饥饿呢,因为某种原因,比如你这个线程优先级可能很低,别的线程优先级很高,所以在调度的时候呢,就调度不到你,结果操作系统只调度优先级高的线程,因为我优先级很低,所以我就调度不到,我就不能继续往下执行,这个时候我就会饿死,因为我分不到足够的资源,那另外的一种情况呢,就比如我们在竞争的时候呢,我竞争这个数据,原子修改,做这个原子操作,我总是失败,总是失败,那么这个时候我也有可能会被饿死,因为我不能往下再走了,这个是多线程当中,可能会碰到的一些问题

下面我们再来看几个重要的概念,有关并发的概念,一般并发呢,阻塞和非阻塞,这个概念我们之前就有讲过,对于非阻塞来讲呢,我们可以进一步分为无障碍的,无锁,无等待,下面我们就这几个级别来做一个讲解

下面我们就对这几个级别来做一个简单的了解,阻塞就是指一个线程进入临界区以后呢,其他线程就必须在这个临界区之外进行等待,等这个线程出来之后呢,他们才能够进去执行

第二个就是无障碍,无障碍是非阻塞,他并不要求一个线程进入临界区之后,其他线程在外面等待,线程是可以自如的进入临界区,那么跟阻塞相比呢,认为他是一种悲观的策略,它会认为说,大家一起修改这个数据呢,很有可能把这个数据改坏的,所以每次我只能允许一个人去修改,而这个非阻塞调度呢,相对来讲比较乐观,如果大家一起修改,也未必把这个数据改坏,所以我可以放开让大家都进来,但是它是一种宽进严出的策略,进的时候所有线程都能够进入临界区,包括读也好,写也好,但是你出来的时候呢,就不一定了,如果他发现线程,临界区的操作呢,跟别人产生了冲突,那么他就会回滚这条数据,比如我们要去读取一对坐标,x,y,坐标系统去读取x,y,他先读X,再读Y,当他读到Y的时候,他发现有其他的线程,改了我这个X,这个时候他就会认为,我再把这个Y读出来是没有用的,我可能会读到一个错误的数据,所以他就会重试,再去重新的读取数据,直到自己读到的X,Y呢,是没有问题的,所以他是一个不断重试的一种策略,所有的线程都相当于在,拿去一个系统当前的快照,他们会一直尝试读取到的快照,有效地为止

第二个级别是无锁,我们刚刚说的无障碍呢,他是说,我所有的线程都进入临界区,但是如果发生了竞争,他并不保证临界区当中的线程能够顺利的出来,因为如果他发现自己的数据呢,每次读取或者每次操作,总是跟别人产生了冲突,他就不停的尝试不停的尝试,如果有10个线程,线程1他修改了以后,改了部分数据,结果他被线程2干扰了,线程2被线程3干扰了,依次类推,线程2又干扰了线程10,如果他们之间都是彼此干扰的,最终会导致所有的线程呢,系统的性能会受到比较大的影响,必须在无障碍的上面呢,加上一个约束,无锁他必须首先是无障碍的,也就是说你所有的线程呢,都必须能够进入这个临界区,但是无锁他加了一个限制条件是说,我保证在我这一次竞争当中,有一个线程是必然能胜出的,那这样他就能够保证说,临界区当中的线程呢,至少有一个是能够顺利走出去的,而不至于所有的全在里面阵亡掉,如果至少有一个线程能够出去,假设里面有100个线程,第一个线程竞争顺利,我们走出了临界区,其他99个再竞争,因为每次竞争肯定保证有一个能够胜利,我们再出去一个,剩下98个再竞争,使得你这个系统,至少是能够顺畅的进行下去,那这就是无锁,下面这段代码是比较典型的在JAVA当中,无锁计算的这么一段代码,在后面我们无锁的程序呢,会在我们后续的课程当中呢,感觉铺天盖地就是无锁的计算

我们下面再来看一下无等待,无锁刚才已经讲了,它是说,我保证每一次至少有一个线程,它是能够在有限步当中完成操作,那么其他的线程呢,在不停的竞争,知道有一个线程为止,那无等待呢,他就更近一步,所以无等待他要求你首先无锁,也就是他保证你能进,他要求所有的线程都能够在有限步上完成,这个要求提的很高,意味着说,你任何线程都能够进入进去,都能够无障碍的进入临界区,并且呢你任何线程呢,都能够在若干步当中,有限步当中,离开临界区,这就会使得你系统的运行呢,会变得非常的顺畅,那么无等待可以说是并行最高级别的了,基本上是可以让每个系统发挥到最好的效率,那么无等待他必然是无饥饿的,因为你所有的线程,都能够有限步的完成,因此你不会永久的出不去,所以他一定是无饥饿的,那无等待的一个典型案例呢,就比方说,我们有读写两个线程,如果说我的读线程,只有读线程,没有写线程,那所有的读线程之间呢,必然是无等待的,原因很简单,因为你所有的进程进来,不能进,因为你读你不会修改数据,所以数据都是一致的,所有的读都是无等待的,但是说如果你有一个写在里面,那么由于写线程会有修改数据呢,所以我们可以提供一种算法,比如有一种算法会这样做,因为我写会影响到读,所以我在每次写之前呢,我把数据全部先拷贝一份副本,我仅拿到一份副本,然后我修改这个副本,而不是修改原始数据,修改数据的过程呢,可能需要一点时间,但是由于我修改的是我副本的数据,而不是你的原始数据,所以这个修改的过程,也不影响线程冲突,因此在这种情况下呢,读线程一样是无等待的,他们都能在有限步中完成操作,所有的写线程,因为每个线程也都是写的副本,他们的写也是无等待的,他们都不需要去做同步,最后需要做同步的是什么呢,只是将写完之后的数据呢,再回写到覆盖原始数据的,而覆盖原始数据的,是非常非常快的,因为我们不需要做大量的操作,或者是一个指针,或者是一个引用,做一个替换而已,那不管哪个写线程胜出,总是能够保证,这个替换上去的数据呢,是一致的,并不会像其他的写的算法一样,我可能会把自己写坏,因为大家写的只是副本,所以必然是安全的,这种方式呢,这个就是等待的一个典型的一个实现,当然无等待的实现是比较麻烦的,而且他有一些技巧性的东西,相对来讲呢,无锁的使用呢,会更加的广泛一些

阿姆达尔他还有一个公式,N个处理器时的,这个加速比,等于一个处理器的加速比,这个公式在这里,处理器的个数是n,我使用N个处理器的时间,是我使用一个处理器的时间,乘上,程序的串行化比例,有多少程序是串行化的,F是串行程序的比重,1-F是并行程序的比重,大家可以看到,我串行程序的比例,加上并行的比例,我并行程序除以n,就是我并行所消耗的时间,加上我串行所消耗的时间,你乘以我原本一个CPU所消耗的时间,就是我n个CPU所消耗的时间,加速比呢,就是我优化前的时候,除以我优化后的时候,我们也可以把这个例子带到这个公式里面去,阿姆达尔告诉我们一个什么问题呢,他就是说,增加处理器的个数,并不能是我们的系统加速比提高,比如我们在这个地方,如果我们的n很大,如果你增加处理器的个数n很大,如果你n很大,但是如果你F很小,导致的结果呢,你这个数字是一个很小的数字,加速比并不会上升,那我们就合理的调整我们的F,串行化比例,和n之间的关系呢,才能够使得加速比比较好,就是你增加CPU是没有用个,如果你程序总体上,串行F总是接近1,下面我们来看一下古斯塔夫森定律,他是类似的

他也是说明处理器个数,串行比例,加速比之间的个数,他这么讲的,他说,程序的执行时间呢,可以分为两部分,串行时间和并行时间,那程序执行需要花多少时间呢,串行的部分,加上并行的部分,总的执行时间呢,a加上n乘以b,n是处理器的个数,那这个总时间呢,我串行执行的时候,总的时间,a加b呢,指的就是,因此呢我的加速比呢,加速前的时间,除以加速后的时间,F是串行在程序中的比例,加速比跟CPU数量的n,看来是有一个直接的线性关系,当我这个F很小的时候,就是串行比例很小,并行比例很高的时候,基本上加速比,CPU的数量呢,基本上成一个线性的关系,基本上强调是说,只要你能够把这个程序,足够的并行化,那加速比就会和你CPU成一个正比,你CPU个数越多,你加速比就越高,从这个角度说明,他们加速比的角度呢,是不同的,说明你光加CPU,是没有用的,你必须提高串行化比例才行,总和这两个定律呢,其实我们的结论是说,你要想让这个程序,通过串行,通过多核,来提高你的性能,那你要做的是两件事,第一处理好你的n,第二处理好你的F

Java高并发程序设计前言相关推荐

  1. java unsafe获取指针_【实战Java高并发程序设计 1】Java中的指针:Unsafe类

    是<实战Java高并发程序设计>第4章的几点. 如果你对技术有着不折不挠的追求,应该还会特别在意incrementAndGet() 方法中compareAndSet()的实现.现在,就让我 ...

  2. 【实战Java高并发程序设计6】挑战无锁算法

    我们已经比较完整得介绍了有关无锁的概念和使用方法.相对于有锁的方法,使用无锁的方式编程更加考验一个程序员的耐心和智力.但是,无锁带来的好处也是显而易见的,第一,在高并发的情况下,它比有锁的程序拥有更好 ...

  3. JAVA高并发程序设计(葛一鸣著)读书笔记

    本文为JAVA高并发程序设计(葛一鸣著)读书笔记.这本书对于刚刚入门的童鞋来讲可能有点深,我推荐可以先看看Java多线程编程核心技术(高洪岩著)一书. 第一章 走入并行世界 什么是同步和异步? 同步就 ...

  4. Java高并发程序设计入门

    转自:http://blog.csdn.net/johnstrive/article/details/50667557 说在前面 本文绝大部分参考<JAVA高并发程序设计>,类似读书笔记和 ...

  5. Java高并发程序设计(三)——JDK并发包(二)

    引言 好久没来学习Java高并发程序设计了,感觉在慢慢遗忘之前学过的内容,今天打算重新拾起. Condition Condition与前两章讲的Object.wait() 和Object.notify ...

  6. 《实战Java高并发程序设计》.pdf

    关注"Java后端技术全栈" 回复"面试"获取全套面试资料 如今,秒杀已经变得十分常见,我们也都习以为常. 然而,从技术的角度来说,秒杀对于Web系统是一个巨大 ...

  7. 《实战 Java 高并发程序设计》笔记——第3章 JDK 并发包(二)

    文章目录 3.2 线程复用:线程池 3.2.1 什么是线程池 3.2.2 不要重复发明轮子:JDK 对线程池的支持 1. 固定大小的线程池 2. 计划任务 3.2.3 刨根究底:核心线程池的内部实现 ...

  8. 《实战Java高并发程序设计》github笔记和源码

    笔记 <实战Java高并发程序设计>中有很多代码范例,适合初学者通过实践入门并发编程,这本书有个问题就是前面的代码都用JDK7,第六章开始又用JDK8了 笔者做了相关笔记并整理源代码,欢迎 ...

  9. 实战java高并发程序设计-笔记进行中

    <JAVA并发编程实践>:出书时间太早,内容比较散,专业术语翻译较早和现在有差异 <Java并发编程的艺术>:手绘图较多文字内容较少,主要讲解并发实现的底层原理和面临的问题,底 ...

最新文章

  1. centeros /redhate密码破解
  2. GRUB与Linux系统修复(第二版)
  3. 8-1-Filter过滤器
  4. Android WebView中图片自适应居中
  5. 面向对象-类与对象、关键字、异常使用
  6. Java基础篇---练习:类的设计
  7. bde连接oracle失败,BDE联接出错,求助
  8. Hive常用窗口函数实战
  9. Linux--原子操作(介绍及其操作函数集)
  10. ubuntu系统安装安卓模拟器(Android SDK)的方法
  11. 金蝶KIS商贸版实现'条码标签打印'功能进行商品条码打印
  12. 微服务架构之公共模块式中创建API接口统一返回结果ApiResult
  13. 学习三部曲之(一):学生为什么学习不好?
  14. MATLAB2016笔记(八):符号数学计算(MATLAB-Maple组件)
  15. 213. 字符串压缩--LintCode领扣编程题
  16. 有限元分析的基本知识
  17. 很可爱的动画片小鸟三号
  18. 选择爬虫代理IP的重要性
  19. 尚硅谷数据仓库实战之1项目需求及架构设计
  20. 【AI视觉】智能送药小车——1.复盘及核心代码

热门文章

  1. ng-repeat 与ng-switch的简单应用
  2. gsm,gprs,cmwap,cmnet,3g,TD-SCDMA,CDMA2000,WCDMA
  3. 在 Linux 上把 MP3 的檔名和 ID3 標簽轉為 UTF-8
  4. 使用腾讯bugly更新服务遇到的坑
  5. 啊~ 五环 你比四环多一环 啊~ 五环 你比六环少一环
  6. springmvc的执行流程详解
  7. Python学习笔记6(列表生成式)
  8. base64文件上传后台处理
  9. 关于apache虚拟主机htttpd的配置实例 (更新中)
  10. 来自web标准margin的嘲笑,你了解我吗?