多线程实战

线程基础:

  1. <线程安全>多个线程访问同一个资源的时候,默认是以排队的方式进行处理,而且,线程执行的顺序不是看书写的顺序,而是看CPU怎么去分配线程的。 所以,线程会不断轮询锁,会造成锁竞争。

  2. <多个线程多把锁> 多个线程多把锁,每个线程都拿到自己指定的锁,分别获得锁之后,执行synchronized方法体的内容。 两个线程,线程获得的就是两个不同的锁,他们互不影响。有一种情况下是相同的锁,即在静态方法上添加synchronized关键字上,表示锁定.class类,使用的是类级别的锁。

  3. <对象锁的同步和异步>

    1. 同步就是存在共享的资源。没有共享的资源就没有必要同步。
    2. 异步就是独立的,相互之间不受到任何的约束。例如使用http的时候,当页面发起ajax请求时,还可以继续浏览和操作页面的内容,两者之间没有任何的关系。
    3. 关键点:原子性(同步)、可见性。
  4. <脏读>

    1. 对于对象的同步和异步的方法,在设计的时候,需要考虑问题的整体性,不然就会出现数据不一致的错误。在oracle中也有这样的ACID,保证数据的正确性,当获取数据的时候,若是其他的客户端修改了这个值,也不会返回给用户看,始终看到的还是旧值。
  5. <Synchronized其他概念>

    1. 方法重入锁:可以锁方法,再去调用锁方法,可以不断的嵌入。
    2. 父子类重入锁: 子类调用父类的方法,这样是线程安全的。
  6. <Synchronized注意细节>

    1. 注意锁的粒度,只锁共享资源的代码区域 ,不要过多的锁住代码,会造成性能降低,大量的线程等待。
    2. 不要使用String常量作为锁,会出现各种问题。
    3. 使用String对象作为锁的时候,不要在线程中修改锁,这样会导致锁失效,因为string 对象已经被修改了。
    4. 使用一个对象作为锁的时候,可以更改属性值,锁不会失效。不能修改对象的引用
  7. volatile:

    1. 概念:主要作用是让变量在多个线程之间可见。
    2. 常见问题:就是线程启动之后,对于线程属性的修改,不会生效。主要原因就是因为JDK1.5之后对于线程的优化,每一个线程在启动之后,都会产生一个自己的独立运行空间,保存需要的属性,所以,在外部修改的属性,无法影响到线程自己的属性。
    3. 线程执行流程:线程启动的时候,会开辟一块空间给线程空间使用,存放主内存的属性,线程执行引擎默认只会从线程空间获取数据,不会到主内存获取数据。而使用了Volatile关键字之后,当数据发生改变时,则强制要求线程引擎去主内存中获取数据。
    4. 缺点:只是保证了线程之间的可见性,无法保证线程的原子性。 可是使用Atomic*类来弥补(atomic类只保证本身方法的原子性,并不保证多次操作的原子性)。
  8. 线程之间的通信

    1. wait和notify:两者都是object的方法,两者必须配合synchronized使用,wait释放锁,notify不释放锁,notify了不会立马通知到wait。
    2. CountDownLatch:不需要使用synchronized,可以实时的通知。
    3. ThreadLocal:是一种多线程之间并发访问变量的解决方案。不提供锁,而是,使每个线程都有一个变量的副本(空间换时间的手法)。在并发不是很高的时候,加锁的性能更好;但是在高并发下或是竞争激烈的情况下,ThreadLocal可以有效降低锁竞争。
    4. 安全的单列模式: 1.静态内部类 2.double check(两次判断obj是否为null)
  9. 并发类容器:

    1. ConcurrentHashMap; ConcurrentSkipListMap(弥补ConcurrentHashMap的不可以排序,相当treeMap)。 把Map分为16个segment(段)。每段相当于一个hashTable。段之间是隔离的,是线程安全的。降低 了锁的粒度。
    2. copy-on-write容器:有两种实现CopyOnWriteArrayList和CopyOnWriteArraySet。
    3. Queue:
      1. ConcurrentLinkedQueue为代表的高性能Queue。 通过无锁的方式,实现高并发下的高性能。它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最后加入的。不允许null元素。
      2. BlockingQueue为代表的阻塞队列。
      3. DelayQueue: 给放入的元素,设置超时时间。 放入的元素类型必须实现Delayed接口。需要重写两个方法getDelay(判断是否到截止时间);comcompareTo();调用take的时候,不是立刻取出元素,而是到了getDelay为负数的时候,才取出数据。
      4. PriorityBlockingQueue: 并不是放入元素的时候,就排好序,而是在take的时候,会根据优先级,拿取到最高优先级的数据。
      5. SynchronousQueue: 是一个没有存储空间的queue,只有先take,才能add,add也不是添加到容器中,而是直接给take使用。
  10. 线程设计模式:

    1. Future模式:重在异步模式的体现。
      1. 需求: 接收到请求,直接返回,然后后台自己处理数据,最后返回真实的数据,当用户真正去看数据的时候,就获取真实数据。 不会让用户一直处于等待数据处理完成状态。
      2. 实现:就是请求时直接返回假数据,使用线程在后台实行操作真实数据,当真的需要真实数据的时候,再去调用准备好的数据。
    2. Master-Worker模式:细化任务,使用多线程,做任务。
      1. 需求:系统由两类进程协作进行。master负责接收和分配任务。worker负责处理子任务,worker处理完任务,再返回给master,进行归纳和总结。从而提高系统的吞吐量。
      2. 实现:
        1. master的工作:1. 使用ConcurrentLinkedQueue去承载所有的任务。 2. 使用HashMap<String, Thread> 去承载所有的worker; 3. 使用ConcurrentHashMap<String, Object>承载每一个并发处理任务的结果集。
        2. Worker的工作:1. 首先得实现Runnable接口; 2. 每一个worker对象需要有master的ConcurrentLinkedQueue的引用; 3. 每一个worker对象需要有master的ConcurrentHashMap的引用;
        3. worker从master的queue去取任务,然后,把自己放入HashMap中,然后最后的结果存入master的ConcurrentHashMap中。
  11. 线程池:

    1. Executors线程池工厂。使用它来创建各种线程池。
      1. newFixedThreadPool(): 返回一个固定数量的线程池,线程池中线程数量始终不变。当有一个任务提交时,若线程空闲,立刻执行,若没有,则暂存在一个任务队列中等待空闲的线程执行。
      2. newSingleExecutor(): 创建一个线程的线程池,当有一个任务提交时,若线程空闲,立刻执行,若没有,则暂存在一个任务队列中等待空闲的线程执行。
      3. newCachedThreadPool(): 返回一个可根据实际情况调整线程数量的线程池,不限制最大数量。若用空闲的线程执行任务,若无任务则不创建线程。并且每一个线程会在60秒后自动回收。
      4. newScheduleThreadPool(): 返回一个ScheduleExecutorService对象,但该线程可以指定线程的数量。
      5. ThreadPoolExecutor:自定义线程池。 会根据使用的是有界队列还是无界队列进行区分。
        1. 有界队列:若有新的任务需要执行,如果线程池实际数量小于corePoolSize,则优先创建线程。若大于corePoolSize,则会将任务加入队列。若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程。若线程数大于 maximumPoolSize,则执行拒绝策略,或是其它自定义行为。
        2. 无界队列:与有界队列相比,除非线程的资源耗尽,否则,不会出现无界队列任务入队失败的现象。当新任务来时,系统的线程数小于corePoolSize,则新建线程执行任务。当达到corePoolSize时,就不会再增加了。若没有空闲的线程资源,则会把任务放入到队列中,直到耗尽系统内存为止。
        3. JDK拒绝策略:
          1. AbortPolicy:直接抛出异常,阻止系统正常运行。
          2. CallerRunPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
          3. DiscardOldestPolicy:丢弃最老的任务,尝试再次提交当前任务。
          4. DiscardPolicy:丢弃任务,不给任何的处理。
    2. Executors的submit和execute的区别:
      1. submit可以传入实现了Callable的接口的实例。
      2. submit方法有返回值。 返回值是一个Future对象,通过其中的get方法,可以知道任务是否执行结束。结束的时候,get方法返回的是null。
  12. 并发包下常用的工具类(concurrent.util):

    1. CountDownLacth: 常用于监听某些初始化任务,等初始化完成之后,通知主线程继续工作。
      1. 实例化countDownLatch的时候,传递的参数,则是需要进行多少次countDown,才能唤醒await;
    2. CyclicBarrier: 所有的线程准备好,同时执行。实例化CyclicBarrier的时候,传递的参数,则是需要进行多少次await,才能一起执行自己线程的内容。一个线程没有准备好,其它线程也无法执行。 两者的区别在于:CountDownLacth针对一个线程,CyclicBarrier针对多个线程。
    3. Callable:
    4. Future:适合在处理非常耗时的业务中,可以有效的减小系统的响应时间,提高系统的吞吐量。TutureTask:对象的get是异步执行的。
    5. 信号量(Semaphore): 新系统上线之前,需要对系统的访问进行合理的评估。保证资源不会被浪费,也不会缺少。
      1. PV(page view): 网站的总访问量,页面浏览量或点击量,用户刷新一次就会被记录一次。
      2. UV(unique visitor): 访问网页的一台电脑客户端为一个访客,一般来讲,时间上以0-24点之内相同的IP只能记录一次。
      3. QPS(query per second): 每秒查询数,很大程度上代表了系统业务上的繁忙程度,每次请求的背后,可能对应着多次磁盘I/O,多次网络请求,多个CPU时间片等等。我们可以通过qps非常直观的了解到系统当前的业务情况。一旦当前的qps超过了设定的阈值,可以考虑增加机器对集群的扩容,以免压力过大导致 宕机,可以根据前期的压力测试得到估值,在结合后期的综合运维情况,估算出阈值。
      4. RT(response time): 请求的响应时间。
      5. 容量评估:一般来说,我们进行多轮压力测试之后,可以对系统进行峰值评估,采用所谓的80/20原则,即80%的访问请求将在20%的时间达到。峰值qps=(总pv * 80%)/ (60 * 60 * 24 * 20%)。然后在将总的峰值qps除以单台机器所能承受的最高的qps值,就是所需要机器的数量:机器数 = 总的峰值qps / 压测得出的单机极限qps。
    6. Semaphore: 指定的参数,就是能被多少个线程同时访问,可以达到一定的限流操作,通过acquire和release进行获取和释放访问许可。 但是,更好的使用redis和nginx来做。
    7. 锁(Lock):除了可以使用synchronized进行锁操作,还可以使用Lock对象。
      1. 重入锁(ReentrantLock): 在需要进行同步的代码部分加上锁定,但是最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远无法进入的结果。 使用的格式就是try, finally结构,保证不管代码是否正确执行,都保证了锁能够正确释放。
      2. Lock锁的通信: 对比synchronized的wait和notify。 Lock使用condition来进行锁之间的通信。获取condition是lock.newCondition; 对应的就是wait对应await,notify对应的是signal; 一个Lock可以产生多个condition,所以多线程之间的交互非常灵活。可以使得部分线程唤醒,其他线程则等待。
      3. 公平锁和非公平锁:公平锁的性能低于非公平锁,因为公平锁需要维护顺序,非公平锁是任意顺序的。
        1. Lock lock = new ReentrantLock(boolean isFair);
        2. 用法:
          1. tryLock(): 尝试获得锁,使用true和false返回结果。
          2. tryLock(): 在给定时间内获取锁,使用true和false返回结果。
          3. isFair(): 是否是公平锁。
          4. isLocked(): 是否锁定。
          5. getHoldCount():查询当前线程保持此锁的个数,也就是调用Lock的次数。
          6. lockInterruptibly(): 优先响应中断的锁。
      4. 读写锁(ReentrantReadWriteLock): 适合在读多写少的场景下,实现读写分离。其本质是分成了两个锁,一个读锁和一个写锁,在读锁的情况下,多个线程可以并发的进行访问,但是在写锁的情况下,只能一个一个顺序的访问。《口诀:读读共享,写写互斥,读写互斥》。 通过ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 获得读锁和写锁: ReadLock readLock = lock.readLock(); WriteLock writeLock = lock.writeLock();
      5. 分布式锁:需要通过第三方框架,如zk之类的,保证多个线程之间代码锁。
  13. 高并发的处理架构:

    1. 网络方面。
    2. 服务业务方面:划分不同的模块,使用nginx/lvs分流 。
    3. java层面上: 限制流量。
  14. Disruptor:

    1. 概况: 完全运行在内存中,使用的是时间驱动方式,能够再无锁的的情况下实现网络的Queue的并发操作。是一个高性能的异步处理框架,轻量级的JMS,也是一个观察者模式的实现,或是监听模式的实现。
    2. 常用术语:
      1. RingBuffer: 负责存储和更新Disruptor中的流通的数据。
      2. Sequence:使用sequence来表示一个特殊组件处理的序号。每一个消费者都维护着一个Sequence。大部分的并发代码依赖在这些Sequence值的运转,因此sequence支持多种当前为AtomicLong类的特性。
      3. Sequencer:这是Disputor的核心。实现了这个接口的两种生产者(单/多生产者)均实现了所有的并发算法,为了在生产者和消费者之间进行快速准确的数据传递。
      4. SequenceBarrier:由Sequencer生成,并且包含了已经发布的Sequence的引用,这些sequence源于Sequencer和一些独立的消费者的Sequence。它包含了决定是否有供消费者来消费的Event的逻辑。
      5. WaitStrategy:决定一个消费者如何等待生产者将Event置入到Disruptor中。
      6. Event:从生产者到消费者过程中所处理的数据单元。用户自定义。
      7. EventProcess:主要事件循环,处理Disruptor中的Event,并且拥有消费者的Sequence。它有一个实现类是BatchEventProcessor,包含Event loop的有效实现,并且将回调到一个EventHandler接口的实现对象。
      8. EventHandler:由用户实现并且代表了Disruptor中的一个消费者接口。
      9. Producer:由用户实现,它调用RingBuffer来插入事件(Event),在Disruptor中没有相应的实现代码,由用户实现。
      10. WorkProcess:确保每一个Sequence只被一个processor消费,在同一个workPool中的处理多个workProcess不会消费同样的Sequence。
      11. WorkerPool: 一个WorkProcessor池,其中workProcessor将消费Sequence,所以任务可以实现workHandler接口的worker之间移交。
      12. LifecycleAware:当BatchEventProcess启动和停止时,于实现这个接口用于接收通知。
      13. 应用场景:可以参考(并发网站:http://ifeve.com/disruptor/)
        1. 可以顺序执行,一个Event在不断在各个handler中处理。
        2. 可以先顺序,再并行的执行。
        3. 简单的实现生产者-消费者:可以只用RingBuffer。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值