当初我买了Office 365 五人共享汽车坐五人版本,并分配了四个...

单例模式是设计模式中使用最为普遍的模式之一它可以确保一些初始化复杂、大对象、核心对象在整个程序中只有一个实例,这不但可以减少内存开销、减轻GC压力(没有頻繁的new操作)也可以保证核心实例的安全性,下面是一个简单的单例模式:

但是这样的写法有个问题假设有下面的写法:

static属性的时候出發了实例的初始化,因为对对象static属性的引用会导致对象的所有static属性以及static{}代码块初始化但是再不需要初始化的时间初始化,可能并不是我們想要的比如一些数据其实还没准备好,那么可以改成这样:

并不在定义的时候初始化而是一开始赋值为null,在getInstance()方法里判断为空时初始囮非空时直接返回。但是注意到为getInstance()放发加上了synchronized因为在并发场景中,可能出现多个线程同时执行初始化这又导致多项城情况下性能下降,结合以上两种情况可以这样写:

这样一样可以控制初始化时间而且不会影响并发场景下的性能,因为只有在SingletonHolder被使用的时候才会导致singleton被实例化只有getInstance()才能触发。

这个还是解决多线程加锁影响性能的问题假设一个对象创建后就不会再改变,只是作为数据传输使用不可能交给任何线程修改内部数据,但会被多线程频繁访问那么这个对象就可以被设计为不变对象。

  • 去掉所有setter()方法以及导致自身属性修改的方法;
  • 所有属性设置为private final保证不可修改;
  • 确保不能继承或子类无法修改任何属性;
  • 提供构造方法初始化对象。
    只要满足以上4点就是一个鈈变对象,它解决并发线程安全的思想是:我不提供修改的可能只开放访问的入口,这样就不要同步了

Java中有很多不变对象,比如所有嘚包装类以及String类这些类都被广泛地在JDK和应用程序中使用,但我们对这些数据的访问从来都不需要加锁因为我们知道这些类型是可靠的,一点初始化就没有改变的可能

生产者-消费者模式是高并发程序中常见的一种设计模式,生产者负责提交任务消费者负责执行任务,兩者之间有一个缓冲队列用于保存提交的任务供消费者使用。这样设计和好地解决了生产者和消费者之间速度不匹配的问题增加了系統的吞吐量,比如kafka就是这样一种模式线程池也是类似的,submit()任务的线程就是生产者线程中正在执行的线程就是消费者,存储任务的BlockingQueue就是緩冲队列

但是问题在于:BlockingQueue是用加锁阻塞方式实现线程同步的,这样必然会带来一定的性能损耗如果用CAS实现线程同步的话就非常好了。

恏消息是Disruptor就是一个基于无锁的队列框架它是一个环形队列RingBuffer,并且初始化的时候必须指定环的大小所以这不是一个无界队列,但内部还昰基于一个普通数组实现的同时环的大小必须是2的整数次幂,因为这样可以使用sequence & (queueSize-1)操作快速将入队的元素放到数组对应的位置上这比取餘快多了。因为环的大小是固定的所以不会出现空间分配和回收,可以做到内存复用减少分配和回收的系统开销,下面直接给出DisruptorArrayBolckingQueue的性能差距测试
先定义一个Event包含需要处理的数据

发现差别在3秒左右,这是在单个consumer的情况下
两个consumer的情况下(我的电脑是双核)

相差在2s左右,当CPU核数越多的时候两者的差距会越来越大。

我们知道BlockingQueue通知消费者有新数据产生的方式是:消费者不停轮询如果队列中有数据就直接返回,否则就挂起直到某个入队操作唤醒,再从队列中返回一个数据Disruptor有多种通知(等待)策略:

  • SleepingWaitStrategy:这个策略也是基于对CPU的保守使用,虽然是在循环中不断等待数据但是它会自旋等待一段时间,如果不成功则使用Thread.yield()让出CPU最终使用LockSupport.parkNanos(1)进行线程休眠,以确保不占用太多的CPU这个策略会囿较高的平均延时,但是对其它线程的影响较小适合用于实时性要求不是特别高而且系统中还有其他核心业务的场景;
  • YieldingWaitStrategy:同样是不断循環,也会使用Thread.yield()让出CPU但是少了自旋和休眠,会有更高的效率但是对CPU就没有上面的写略那么友好了,消费线程只是一个执行了Thread.yield()的死循环所以消费线程最好低于系统的逻辑CPU数量(4核8线程的8),否则整个应用程序都可能受到影响——除非这个系统只干消费这么一件事这种策畧适合于低延迟但又为其他线程保留可执行余地的场景;
  • BusySpinWaitStrategy:这是最疯狂的一种策略,就是一个死循环!消费线程会尽最大努力疯狂从队列Φ获取数据因此它会毫不犹豫地吃掉所有CPU资源,所以消费线程数一定得小于物理CPU数量(4核8线程的4)这种策略用于真的非常繁忙或对实時性要求较高的场景。

除了CAS、等待策略等会影响整个队列的吞吐量以外还有一个可能会影响性能的点就是伪共享汽车坐五人。为了提高CPU速度CPU有一个高速缓存Cache,每次读取一个变量时会把邻近的几个变量一起拉到高速缓存中(缓存行,一般为64字节)因为CPU访问高速缓存的速度非常快。但是这个缓存优化却是一个潜在的性能杀手:假设有两个CPU C1和C2有两个队列中的数据A、B,假设A/B同在一个缓存行且同时被C1和C2拉到各自的高速缓存这时C1更新了A,C1为了保障修改的值被C2看到基于内存屏障的机制,会将修改的变量立即刷新到主内存中;这时候C2中的缓存荇因为包含A会导致整个C2的缓存行失效,即使C2只关心B也只能到主存中取值如果CPU经常不能命中缓存,队列的吞吐量就会急剧下降这就是偽共享汽车坐五人。

那么Disruptor是怎么解决伪共享汽车坐五人的问题的呢就是通过缓存行填充。既然每个CPU会将访问的变量相邻的64字节的变量拉倒自己的内存空间那么可以在该变量上再新建几个空变量满足64个字节不就可以了么。即使出现伪共享汽车坐五人也不会影响其他CPU。所鉯在DisruptorRingBuffer源码中可以看到有几个Long类型的变量P1,P2,P3,P4,P5,P6,P7就是为了填充,比如频繁使用的sequence变量

从上面的例子看来,伪共享汽车坐五人对性能的影响还昰肉眼可见的对于类似数组这种连续地址的频繁更新操作,伪共享汽车坐五人优化还是有必要的

Future模式是很常见的一种并发设计模式,咜的核心思想是异步调用它不需要线程立即返回真实结果,只返回一个契约调用者线程依然可以毫无阻碍地做其他事情,等到将来需偠异步线程的结果的时候再根据先前的契约获取结果获取结果的过程是阻塞的。

Future相比于Thread.join()后者会导致等待线程结果的这段时间,调用者線程无法做任何事情即使知道异步线程需要耗费相当长的时间;而前者会立即返回,线程可以继续做其他事情即使做其他事情的过程Φ所有异步线程就已经结束也无需担心,任何时候都可以凭借"契约"得到结果

Java中有一套实现好的Future模式,使用起来也非常方便:

一般情况下Future鈈这么使用都是配合ExecutorService线程池来使用的,线程池的submit()方法都会返回一个Future每次提交任务到线程池中就可以得到一个"契约",可以根据这个契约判断任务是否完成或者获取结果

CompletableFuture。普通的Future还是有一定的局限性的如果说Future让调用者获取线程结果的操作变得更加主动和可控,那么它也應该提供线程之间相互依赖时的解决方案比如一个Future依赖于另一个Future的结果,但不幸的是并不能直接实现这样的操作但如果依然使用普通線程的等待、阻塞、join()等操作,那Future的优势荡然无存CompletableFuture就是线程之间相互依赖的Future解决方案。

CompletableFuture是我再看Java8心特性的时候发现的具体的骚操作移步:,这里有详细的讲解几乎可以满足绝大部分线程依赖的场景,而且同时提供了阻塞和非阻塞的方法

假设一个任务有ABCD四个步骤,并且烸个步骤都依赖于前一个步骤的结果这样的任务是没有必要并行的,不如在一个任务里一次执行ABCD四个步骤就行了

但是任务数量很多的時候情况就有些不一样了:如果使用一个任务一个任务地执行肯定会很慢,但是如果用一批线程来做那会让很多任务一直没有开始,最後任务完成也是分散不连续的可能会影响吞吐量;把所有步骤写在一个线程也不是很好的办法,当任务失败需要重试的时候甚至不知道從哪一步开始重试流水线可以解决这两个问题,它既可以达到多线程的处理速度也可以增加系统的吞吐量,甚至可以记录各个步骤的狀态实现重试时忽略已经完成的步骤:

可以看出多线程和流水线耗时是差不多的,如果多线程中有两个任务的某个步骤卡住了就会长時间丢失两个可用线程,即使这两个线程可以在等待的时间里做其他事流水线即使有某些步骤卡住了,其他步骤还是会正常执行;而且鋶水线因为每个步骤都有缓存每个步骤又可以用多个线程来消耗,所以可以增加系统的吞吐量

并行搜索意味着各个搜索线程之间会有通信机制,因为一旦某一个线程搜索到结果其它线程就可以结束了。并行搜索的实现也并不困难:将数据集分成几部分然后各部分单獨搜索即可,一旦某个线程搜索到结果就通知其它线程结束并返回这个搜索结果

最值得思考的部分是如何进行线程之间的通信,最简单嘚方法是设置一个公共变量如果某个线程搜索到了结果就更改这个变量(volatile + synchronized),其它线程发现更新就主动中断当前线程

前面JDK并发包中提到过Fork/Join框架和ForkJoinPool,采用递归的方式定义任务拆分和合并的规则不断的使用分治的思想,到一定规模后启动进程计算Fork/Join的独特之处在于工作窃取(Work-Stealing):當Fork/Join模式下的某个线程执行完毕处于空闲状态,它可以到别的线程的任务队列队尾获取任务执行使得多核、多线程的优势发挥到最大。

MapReduce的核心思想跟Fork/Join是一样的都是分而治之。MapReducemap阶段和reduce阶段每个阶段都是用键值对(key/value)作为输入和输出。而程序员要做的就是定义好这两个阶段的函数:map函数和reduce函数客户端配置好mapreduce,以job的形式提交给JobTracker;接着JobTracker将需要的文件复制到HDFS包括MapReduce程序打包的JAR文件、配置文件和客户端计算所嘚的输入划分信息,然后将任务放到任务队列;任务调度器根据自己的算法调度到改作业为每个划分创建一个map并提交到TaskTrackerTaskTracker根据主机核的數量和内存的大小有固定数量的map槽和reduce槽其中map任务会分配给对应主机上有目标数据块的TaskTracker,同时把jar包复制到该机器上(数据本地化运算移动、数据不移动);TaskTracker每隔一段时间会给JobTracker发送一个心跳,告诉JobTracker它依然在运行同时心跳中还携带着很多的信息,比如当前map任务完成的进度等信息当JobTracker收到作业的最后一个任务完成信息时,便把该作业设置成“成功”;最后进入reduce阶段拷贝不同分区的数据到不同的TaskTracker节点,执行完毕后寫入HDFS

可以看出Fork/Join更像是单机版的MapReduce增加了工作窃取,同时MapReduce的任务状态监听变成了多线程之间的通信和控制;MapReduce像是分布式的Fork/Join没有工作窃取。MapReduceFork/Join的执行时间都取决于执行时间最长的节点/线程但是Fork/Join为了让这种影响减少到最小,使用了工作窃取算法来尽量加快最慢线程的执行

NIONew IO嘚简称,是一套可以完全替代Java IO的新的IO机制严格来说,NIO与并发其实没有直接联系但是用NIO可以大大提高并发处理效率。而并发较高的场景┅般是Socket网络读写因此这里基本只讨论网络IO。

模拟高并发的客户端Client:

发现服端每个请求几乎要耗时6s处理假设并发的规模足够大,每个请求速度又不够快那么服务器的资源将会很快被耗尽,或者能够处理的并发数大幅减少然而导致服务器处理性能下降得并不是服务器本身资源或性能问题,而是CPU一直在等待网络IO说得更直白一点,就是一直处于空闲但是又无能为力

在等待IO的这段时间内,完全可以把CPU让出來给其他真正需要IO的线程使用如何将等待IO的时间分离出来就是NIO可以做到的事。NIO的几个重要概念这里用快递业务来做类比:

  • Channel:通道,一個Channel对应一个Socket如果把客户端和服务端的Socket比作收发货地址,那Channel可以看做发货地址与收货地址间的固定线路;
  • Buffer:是一个byte数组所有发往Chanel的数据必须打包成一个Buffer,所以Buffer可以看作一个打包好的包裹这个包裹可以从收货地址发出,也可以从发货地址发出;
  • Selector:现在客户端和服务端有了通道、定义好了数据交互方式但是彼此不知道对方什么时候发送的数据,也不知道什么时候接收数据这时候就需要一个Selector来管理通道,Channel先到Selector中注册当某个Channel有数据准备好时,Selector就会接到通知得到那些数据,通过合适的方式处理并传输这些数据所以Selector可以看作是一个快递员,他负责多个路线的快递的收件和派送收寄双方不需要直接通知对方,只需要把包裹交给快递员就好了快递员完成一系列的内部处理(數据处理)后,再把处理后的数据送往目的地

这样的好处是多个请求可以由一个或极少数的Selector管理,当Channel中有数据准备好的时候Selector开始工作,否则处于等待状态但是因为Selector是极少数的,不必担心占用太多的服务器资源我们现在用NIO来构造一个Socket-Server-Client,Server:

启动服务端后再启动客户端最後服务端输出:

服务端并不会一直阻塞。

AIOAsynchronized IO是完全异步的IO。在NIO中虽然不用一直等待IO,IO操作准备好时再通知线程处理但是IO操作本身仍嘫是同步的,只是节省了等待的时间IO操作本身消耗的时间是仍然可以感知到的。

AIO不同于NIO的地方在于:IO操作完成后再给线程发出一个通知因此AIO完全不会阻塞,所以相应地IO操作完成后会执行一个回调函数,由系统触发

这个Server就简单地把客户端传来的数据返回给客户端,与NIO鈈同的点在于:accept()方法不再阻塞而是立马返回,并注册一个CompletionHandler实例处理客户端连接,其中的两个方法分别会在成功或失败的情况下被调用;read()/write()方法不再阻塞所以想要等待返回结果必须使用Future.get();整个线程都是异步且是驻守后台的,如果想要服务始终保持需要主动阻塞。

先后启動Server和ClientIO操作的结果全都定义在CompletionHandler里,IO真正执行完毕后会触发这个Handler的方法服务端完成数据处理和对客户端回复,客户端发送数据并接收服务端的应答

互联网发展到今天已经彻底改變了人们的生活方式。想当年一台收音机、一部旧电视、一份散发着打印机墨香的报纸、一把藤椅就能把爷爷辈的生活填满。而现在掱机几乎成了人类业余生活的全部,更有调侃声称“如果有来生愿做你的手机伴你左右”。

用户习惯变了广告的形式也发生了翻天覆哋的变化。传统的电视、报纸、广播、海报等广告摇身一变成了人们掌心一幕幕有趣的H5场景每秒都出现在不同人的朋友圈。H5广告用其强夶的魅力正在试图颠覆广告行业。在传统广告与H5广告之中超过70%的企业更偏向于投资打造H5广告。传统广告与H5广告的差别到底在哪里呢

傳统广告总体价格比H5广告要贵。举个例子在一份主流媒体报纸上刊登广告,1/4版面全彩的一次报价超过5000元电视广告更是每分钟几十万甚臸上百万,而H5广告中小企业基本可以借助免费工具或者花少量资金购买模板改造,就算是天猫双十一这种营销团队打造的H5广告一次也僦几十万而已。

传统广告基本停留在展示层面如电视上播一段广告片,电台一段语音广告虽说广告片也能呈现各种创意,像你们现在還看不懂的百岁山广告却因缺乏互动,带给用户就是与我无关遥不可及的感觉H5广告有多样化的交互体验方式,比如用户能在一个H5广告Φ达到娱乐消遣的目的新奇有趣,能最大限度俘获客户的心

传统广告虽然覆盖所有传播媒介,但我们对视频网站弹出来的广告是无比厭恶的H5广告根植手机,主要驻扎地在拥有超过9亿用户的微信结合多样化的交互功能,用户能够主动分享

一个传统广告的上线,有复雜的审批流程H5广告做好后生成链接,直接分享就能达到传播效果对热点营销是十分有利的。

传统广告是“创意+技术+文案”主打视觉效果。H5广告是“创意+文案+设计+情感”除了视觉,还要求听觉、触觉配合的完美体验

要是在前几年,手机没有普及的时候H5广告营销注萣是失败的。但H5广告准确说来是HTML5,出现在最好的年代一个平均每人要查看手机150次的年代,一个44%的人睡觉都要把手机放在身边的年代風靡全球是可以预见的。选择传统广告还是H5广告最终还是要看企业的产品定位,双管齐下才是最有效的宣传方式

互联网+是大势所趋,無论是广告转型还是企业转型谁最快地进驻互联网,谁就能在营销上快人一步

这里推荐一下我的学习交流群:,里面都是学习前端的如果你想制作酷炫的网页,想学习编程从最基础的HTML+CSS+JS【炫酷特效,游戏插件封装,设计模式】到移动端HTML5的项目实战的学习资料都有整悝送给每一位前端小伙伴,有想学习web前端的或是转行,或是大学生还有工作中想提升自己能力的,正在学习的小伙伴欢迎加入

我要回帖

更多关于 共享汽车坐五人 的文章

 

随机推荐