0%

JUC

J.U.Cjava.util.concurrent的简写,里面提供了很多线程安全的集合。

常用工具类

  • atomic(内部原理unSafe. 实现 : 死循环)
    • AtomicInteger
    • AtomicDouble
    • AtomicLong
    • AtomicReference
    • LongAcc
  • lock
    • AQS
    • LOCK
    • Condition
    • LockSupport
      • 两大方法:Park/unPark
      • 暂停线程方式
        • LockSupport.park();
        • Object.wait();
        • Thread.Sleep();
    • ReadWriteLock
    • ReentrantLock
      • 锁对变量加锁后,线程是否可再次执行判断是否可重入
  • 工具
    • CountdownLatch
    • CyclicBarrier
    • Semaphore
    • FutureTask
    • Callable
  • 集合
    • ConcurrentHashMap
    • CopyOnWriteArrayList
    • BlockingQueue
    • BlockingDeque
  • 线程池
    • Executors

线程安全

通过加锁保证数据的一致性.也就是说当一个线程访问某个数据时,通过加锁操作对数据进行保护,其它线程在加锁期间不能对其访问

线程封闭

当多个线程访问共享变量时,需要通过加锁来保证数据同步,增加了程序的复杂性. 避免数据同步的一种方式是不共享变量,比如使用局部变量和ThreadLocal

线程调度

系统为线程分配CUP使用权的过程

  • 协同式线程调度

线程的执行时间由线程自己控制,当自己执行完后,主动通知操作系统切换到另外一个线程上执行. 好处是实现简单,线程对自己的操作是可知道的,没有什么线程同步问题.缺点是线程执行时间不可控,如果一个线程有问题,可能会一致阻塞在那里.

  • 抢占式线程调度

每个线程的执行时间有操作系统分配,线程的切换不由线程本身决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)线程执行时间系统可控,也不会有一个线程导致进程阻塞.

java线程调度就是抢占式调度

可以通过设置线程的优先级让一些线程尽可能的先执行多执行(Java一共有10个线程优先级从Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于ready时,优先级越高越容易被执行.但优先级并不靠谱,因为Java线程时通过映射到原生线程来实现的,所以线程调度还是取决于操作系统.

状态转换

  • 新建(New)创建后尚未启动的线程
  • 运行(Runnable):Runnable包括操作系统中的Running和Ready. 处于此状态的线程有可能在运行,也有可能在等待CPU为它分配执行时间.线程创建后,其它线程调用了该线程的start方法,那么该线程就位于可运行线程池中,变得可运行,就差CPU分配执行时间,其它运行所需要的资源都已经获得.
  • 无限期等待(Waiting):该状态下的线程不会被分配CPU执行时间,要等待被其它线程进行显示唤醒. 如没有设置timeout的Object.wait()方法和Thread.join()方法,以及LockSupport.park()方法
  • 限时等待(Timed Waiting):该状态下的线程不会被分配CPU执行时间,只不过不需要被显示的唤醒,在一定时间后会被系统自动唤醒. 如Thread.sleep(),设置了timeout的Object.wait()和Thread.join(),LockSupport.parkNanos()以及LockSupport.parkUntil()方法
  • 阻塞(Blocked):线程被阻塞了,与等待的区别是:阻塞线程在等待一个排它锁.

阻塞状态是因为某种原因放弃CPU使用权,暂时停止执行,直到线程进入就绪状态,才有机会转到运行状态.

  • 结束(Terminated):线程执行完了或者异常退出了run()方法,该线程结束生命周期

阻塞常见的三种情况

1.等待阻塞(无限期等待):运行的线程执行wait()方法,该线程会释放占用的资源,JVM会把该线程放入等待池.进入这个状态后,线程不会自动唤醒,必须依靠其它线程调用notify()或notifyAll()方法才能会被唤醒.

2.同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其它线程占用,则JVM会把该线程放入锁池.
3.其它阻塞(限时等待):运行的线程执行了join()或者sleep()方法,或者发起了I/O请求,JVM会把该线程置为阻塞状态,当sleep()状态超时,join()等待线程终止或者超时,I/O处理完成,该线程重新转入就绪状态.

同步容器

同步容器通过synchronized关键字修饰容器,保证同一时刻只有一个线程使用容器,从而使容器线程安全. synchronized的意思的同步.

1.Vector和ArrayList都实现了List接口,Vector对数组的操作和ArrayList都一样,区别在于Vector在可能出现线程安全的方法上都加了synchronized关键字修饰.

2.Stack是Vector的子类,Stack实现的是先进后出,在出栈入栈都进行了synchronized修饰.

3.HashTable:它实现了Map接口,操作和HashMap一样(区别:HashTable不能存null,HashMap键值都可以为null),HashTable的所有操作都加了synchronized修饰.

4.Collections提供了线程同步集合类

1
List list=Collections.synchronizedList(new ArrayList());Set set=Collections.synchronizedSet(new HashSet());Map map=Collections.synchronizedMap(new HashMap());

并发容器

并发容器是指允许多线程访问的容器,并保证线程安全.为了尽可能提高并发,Java并发工具包中采用多种优化方式来提高并发容器的执行效率,核心就是锁,CAS(无锁),COW(读写分离),分段锁.

1.CopyOnWriteArrayList

CopyOnWriteArrayList相当于实现了线程安全的ArrayList,在对容器写入时,Copy出一份副本数组,完成操作后把副本数组的引用赋值给容器,底层是通过ReentrantLock来保证同步. 但它通过牺牲容器的一致性来换取容器的并发(在Copy期间读取的还是旧数据),所以不能在强一致的场景下使用.

2.CopyOnWriteArraySet

CopyOnWriteArraySet和CopyOnWriteArrayList的原理一样,它是实现了CopyOnWrite机制的Set集合.

3.ConcurrentHashMap

ConcurrentHashMap相当于实现了线程安全的HashMap,Key是无序的,并且key和value都不能为null,在JDK8之前,采用分段锁的机制来提高并发,只有在操作同一段键值对是才需要加锁.JDK8以后才用CAS算法提高容器的并发.

4.ConcurrentSkipListMap

ConcurrentSkipListMap相当于实现了线程安全的TreeMap,key是有序的,key和value不允许为null,它采用跳跃表的来替代红黑树,原因是红黑树在插入或者删除节点时需要做旋转调整,导致要控制的粒度太大.而跳跃表使用的是链表,利用CAS算法实现高并发线程安全.

5.ConcurrentSkipListSet

ConcurrentSkipListSet和ConcurrentListMap的原理一样,它是实现了线程安全的TreeSet

强一致性

系统中某个数据更新后,后续任何对该数据的读取都将获取到最新的值,在任意时刻,所有节点中的数据是一样的。对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。

弱一致性

系统中某个数据被修改后,后续对该数据的读取有可能获得更新之后的值,可能获得更新前的数据,但经过不一致的窗口这段时间,后续对该数据的读取将获得更改之后的值.

最终一致性

是弱一致性的特殊形式,存储系统在保证没有更新的情况下,最总所有对该数据的访问都会得到更新后的数据.不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化。

来源:

https://www.bilibili.com/video/BV1G44y1q7qu

https://rumenz.com/rumenbiji/java-Multithreading.html

https://rumenz.com/rumenbiji/java-synchronized-concurrent-container.html

ThreadPool

初始化线程池后,把任务丢进去,等待调度就可以了,使用起来比较方便。
JAVA中Thread是线程类,不建议直接使用Thread执行任务,在并发数量比较多的情况下,每个线程都是执行一个很短的时间就任务结束了,这样频繁创建线程会大大降低系统的效率,因为频繁的创建和销毁线程需要时间。而线程池可以复用,就是执行完一个任务,并不销毁,而是可以继续执行其它任务。

Thread的弊端

  • 每次new Thread() 创建对象,性能差。
  • 线程缺乏统一管理,可能无限制创建线程,相互竞争,有可能占用过多系统资源导致死机或OOM。
  • 不能多执行,定期执行,线程中断

线程池的优点

  • 重用存在的线程,减少对象创建,消亡的开销,性能佳,降低资源消耗。
  • 可以控制最大并发线程数,提高系统资源利用率,同时避免过多资源竞争,避免阻塞,提高响应速度。
  • 提供定时执行,定期执行,单线程,并发数控制等功能,以提高线程的可管理性。

阿里发布的 Java 开发手册中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors利用工厂模式向我们提供了4种线程池实现方式,但是并不推荐使用,原因是使用Executors创建线程池不会传入相关参数而使用默认值所以我们常常忽略了那些重要的参数(线程池大小、缓冲队列的类型等),而且默认使用的参数会导致资源浪费,不可取。

阅读全文 »

设计模式学习方法及实践

学习

  • 牢记类的继承/实现关系
  • 多记模式在现实中对应的场景

实践

  • 抽象类的职责
  • 多看源码的实践及解决的问题

常用实践

  • 单例
    • 全局配置
    • 无状态实体
  • 工厂
    • HashMap
  • 模板
    • 流程固定,实现类多
      • redisTemplate/jdbcTemplate
  • 监听
    • 领域事件
  • 策略
    • 一个借口,多个Bean实现
  • 适配
    • 实体类转换
  • 代理
    • 统一封装第三方接口调用

阅读源码方式

寻找入口

  • Tomcat-Main函数
  • Debug常用方法

划分模块/组件

  • 模块名,包名
  • 套用设计模式

串联链路

  • Request/Response
  • SpringBean
  • SQL

事务

ACID

  • Atomicity

    原子性(Atomicity):事务中的操作要么都做,要么都不做。

  • Consistency
    一致性(Consistency):系统必须始终处在强一致状态下

  • Isolation
    隔离性(Isolation):一个事务的执行不能被其他事务所干扰

  • Durability

    持续性(Durability):一个已提交的事务对数据库中数据的改变是永久性的

BASE

概念

  • Basically Avaliable
    基本可用(Basically Available):系统能够基本运行、一直提供服务
  • Soft-state
    状态(Soft-state):系统不要求一直保持强一致状态
  • Eventual consistency
    最终一致性(Eventual consistency):系统需要在某一时刻后达到一致性要求

主要实现方式

  • 两阶段型
    分布式事务两阶段提交,对应技术上的XA、JTA/JTS。这是分布式环境下事务处理的典型模式
  • 补偿型
    TCC型事务(Try/Confirm/Cancel)可以归为补偿型;TCC思路是:尽早释放锁;在Try成功的情况下,如果事务要回滚,Cancel将作为一个补偿机制,回滚Try操作;
    TCC各操作事务本地化,且尽早提交 (放弃两阶段约束);当全局事务要求回滚时,通过另一个本地事务实现“补偿”行为;
    TCC是将资源层的两阶段提交协议转换到业务层,成为业务模型中的一部分;
  • 异步确保型
    将一些同步阻塞的事务操作变为异步的操作,避免对数据库事务的争用,典型例子是热点账户异步记账、批量记账的处理
  • 最大努力通知型
    交易的消息通知与失败重试(例如商户交易结果通知重试、补单重试)

模式分类

  • 异步确保
  • 重试与幂等
  • 可补偿操作

CAS

什么是CAS

CAS(compare and swap),字面意思比较并交换,是解决多线程并行情况下使用锁造成性能损耗的一种机制.

1
2
3
public final boolean compareAndSet(int expect, int update) {        
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

CAS有三个操作数,valueOffset内存值,expect期望值,update要更新的值。如果内存值(valueOffset)和期望值(expect)是一样的。那么处理器会将该位置的值更新为(update),否则不做任何操作。

CAS 有效地说明了“我认为位置valueOffset应该包含值expect,如果包含该值,则将update放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”在 Java 中,sun.misc.Unsafe类提供了硬件级别的原子操作来实现这个 CAS,java.util.concurrent包下的大量类都使用了这个Unsafe类的 CAS 操作

阅读全文 »

MySQL 分库分表

什么是分库分表

分库分表其实很好理解,「顾名思义,即把存于一个库的数据分散到多个库中,把存于一个表的数据分散到多个表中」。但是需要明确一点,分库分表不是一件事,而是三件事,也就是「分库分表的三种方案」

  • 「只分库不分表」
  • 「只分表不分库」
  • 「既分库又分表」
阅读全文 »

ThreadLoacl的理解

ThreadLocal提供了线程的局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但不会和其他线程的局部变量发生冲突,实现了线程的数据隔离

简言之:往ThreadLoacl中填充的变量属于当前线程,该变量对其他线程而言是隔离的。

ThreadLocal可以让我们拥有当前线程的变量

常用的方法

  • set(T value):设置线程本地变量的内容。
  • get():获取线程本地变量的内容。
  • remove():移除线程本地变量。注意在线程池的线程复用场景中在线程执行完毕时一定要调用remove,避免在线程被重新放入线程池中时被本地变量的旧状态仍然被保存。
阅读全文 »

Volatile

volatile的中文意思是不稳定的、易变的,用volatile修饰的变量是为了保证变量的可见性

  • Volatile作用:用来确保将变量的更新操作通知到其他线程

  • 原理:

    1)编译器会注意到被volatile修饰的变量是共享的,因此不会将该变量上的操作与其他内存操作一起重新排序

    volatile变量不会被缓存在寄存器中,因此读取volatile变量总会返回最新的值

    2)在访问volatile变量时,不会执行加锁操作,因此也不会阻塞线程,对非volatile变量进行读写时,每个线程先从内存拷贝变量到CPU缓存中

    总结:不会重排、不会缓存(永远是最新值)、不会加锁(因此不会阻塞)

阅读全文 »

DDD与中台、微服务的关系

  • 什么是中台
  • 企业中台组成
  • 基于DDD的微服务设计
  • 微服务拆分和设计原则
  • 中台、微服务与DDD的关系
阅读全文 »