Java并发 -- 死锁
Account.class 在《Java并发 – 互斥锁》中,使用了Account.class作为互斥锁来解决银行业务的转账问题 虽然不存在并发问题,但所有账户的转账操作都是串行的,性能太差 例如账户A给账户B转账,账户C给账户D转账,在现实世界中是可以并行的,但该方案中只能串行 账户和账本 每个账户都对应一个账本,账本统一存放在文件架上 银行柜员进行转账操作时,需要到文件架上取出转出账本和转入账本,然后转账操作,会遇到三种情况 如果文件架上有转出账本和转入账本,都同时拿走 如果文件架上只有转出账本或只有转入账本,那需要等待那个缺失的账本 如果文件架上没有转出账本和转入账本,那需要等待两个账本 两把锁123456789101112131415161718public class Account { // 账户余额 private int balance; // 转账 public void transfer(Account target, int amt) { // 锁定转出账户 synchronized (this...
Java并发 -- 互斥锁
解决什么问题互斥锁解决了并发程序中的原子性问题 禁止CPU中断 原子性:一个或多个操作在CPU执行的过程中不被中断的特性 原子性问题点源头是线程切换,而操作系统依赖CPU中断来实现线程切换的 单核时代,禁止CPU中断就能禁止线程切换 同一时刻,只有一个线程执行,禁止CPU中断,意味着操作系统不会重新调度线程,也就禁止了线程切换 获得CPU使用权的线程可以不间断地执行 多核时代 同一时刻,有可能有两个线程同时在执行,一个线程执行在CPU1上,一个线程执行在CPU2上 此时禁止CPU中断,只能保证CPU上的线程不间断执行,但并不能保证同一时刻只有一个线程执行 互斥:同一时刻只有一个线程执行 如果能保证对共享变量的修改是互斥的,无论是单核CPU还是多核CPU,都能保证原子性 简易锁模型 临界区:一段需要互斥执行的代码 线程在进入临界区之前,首先尝试加锁lock() 如果成功,则进入临界区,此时该线程只有锁 如果不成功就等待,直到持有锁的线程解锁 持有锁的线程执行完临界区的代码后,执行解锁unlock() 锁和资源 synchronized12345678910111213141...
Java并发 -- Java内存模型
解决什么问题Java内存模型解决了并发程序中的可见性问题和有序性问题 Java内存模型按需禁用 CPU缓存会导致可见性问题,编译优化会导致有序性问题 解决可见性和有序性最直接的办法:_禁用CPU缓存和编译优化_ 问题虽然解决了,但程序性能会大大下降 合理的方案:_按需禁用CPU缓存和编译优化_ 为了解决可见性和有序性的问题,只需要给程序员提供按需禁用CPU缓存和编译优化的方案即可 程序员视角 Java内存模型规范了_JVM如何提供按需禁用CPU缓存和编译优化的方法_ 具体方法包括:volatile、synchronized和final关键字,以及六个Happens-Before规则 volatile volatile关键字不是Java语言的特产,在古老的C语言也有,最原始的意义就是_禁用CPU缓存_ volatile int x = 0:告诉编译器,对这个变量的读写,不能使用CPU缓存,必须从内存中读取或者写入 Java代码1234567891011121314151617public class VolatileExample { private int x = 0...
Java并发 -- 问题源头
CPU、内存、IO设备 核心矛盾:三者的_速度差异_ 为了合理利用CPU的高性能,平衡三者的速度差异,计算机体系结构、操作系统和编译程序都做出了贡献 计算机体系结构:CPU增加了缓存、以均衡CPU与内存的速度差异 操作系统:增加了进程、线程,分时复用CPU,以平衡CPU和IO设备的速度差异 编译程序:优化指令执行次序,使得缓存能够得到更加合理地利用 CPU缓存 -> 可见性问题单核 在单核时代,所有的线程都在一颗CPU上执行,CPU缓存与内存的数据一致性很容易解决 因为所有线程操作的都是同一个CPU的缓存,一个线程对CPU缓存的写,对另外一个线程来说一定是可见的 可见性:一个线程对共享变量的修改,另一个线程能够立即看到 多核 在多核时代,每颗CPU都有自己的缓存,此时CPU缓存与内存的数据一致性就没那么容易解决了 当多个线程在不同的CPU上执行时,操作的是不同的CPU缓存 线程A操作的是CPU-1上的缓存,线程B操作的是CPU-2上的缓存,此时线程A对变量V的操作对于线程B而言不具备可见性 代码验证123456789101112131415161718192021222324...
Java并发 -- 概述
核心问题分工 JUC中的Executor、Fork/Join和Future本质上都是一种分工方法 并发编程领域还总结了一些设计模式,基本上都是和分工方法相关 生产者-消费者 Thread-Per-Message Worker Thread 同步 在并发编程领域里的同步,主要指的就是_线程间的协作_ 一个线程执行完了一个任务,如何通知执行后续任务的线程开始工作 协作一般是与分工相关的 JUC中的Executor、Fork/Join和Future本质上都是一种分工方法 但同时也能解决线程协作的问题 例如,用Future可以发起一个异步调用 当主线程调用get()方法取结果时,主线程会等待 当异步执行的结果返回时,get()方法就自动返回了 Future工具类已经帮我们解决了_主线程和异步线程之间的协作_ JUC中的CountDownLatch、CyclicBarrier、Phaser和Exchanger都是用来解决线程协作问题的 但很多场景还是需要自己处理线程之间的协作,问题基本可以描述为 当某个条件不满足时,线程需要等待,当某个条件满足时,线程需要被唤醒执行 在Java并发编程领...
Kafka -- 可靠性
可靠性保证 可靠性保证:确保系统在各种不同的环境下能够发生一致的行为 Kafka的保证 保证_分区消息的顺序_ 如果使用同一个生产者往同一个分区写入消息,而且消息B在消息A之后写入 那么Kafka可以保证消息B的偏移量比消息A的偏移量大,而且消费者会先读取消息A再读取消息B 只有当消息被写入分区的所有同步副本时(文件系统缓存),它才被认为是已提交 生产者可以选择接收不同类型的确认,控制参数acks 只要还有一个副本是活跃的,那么已提交的消息就不会丢失 消费者只能读取已经提交的消息 复制 Kafka可靠性保证的核心:_复制机制_ + 分区的多副本架构 把消息写入多个副本,可以使Kafka在发生崩溃时仍能保证消息的持久性 Kafka的主题被分成多个分区,分区是基本的数据块,分区存储在单个磁盘上 Kafka可以保证分区里的事件总是有序的,分区可以在线(可用),也可以离线(不可用) 每个分区可以有多个副本,其中一个副本是首领副本 所有的事件都直接发送给首领副本,或者直接从首领副本读取事件 其他副本只需要与首领副本保持同步,并及时复制最新的事件即可 当首领副本不可用时,其中一个同步副本将成为新的...
Kafka -- 内部原理
群组成员关系 Kakfa使用ZooKeeper来维护集群成员的信息 每个Broker都有一个唯一的ID,这个ID可以在配置文件里面指定,也可以自动生成 在Broker启动的时候,通过创建临时节点把自己的ID注册到ZooKeeper Kakfa组件订阅ZooKeeper的/brokers/ids路径,当有Broker加入集群或者退出集群时,Kafka组件能获得通知 如果要启动另一个具有相同ID的Broker,会得到一个错误,这个Broker会尝试进行注册,但会失败 在Broker停机,出现网络分区或者长时间垃圾回收停顿时,Broker会从ZooKeeper上_断开连接_ 此时,Broker在启动时创建的临时节点会从ZooKeeper上自动移除(ZooKeeper特性) 订阅Broker列表的Kafka组件会被告知该Broker已经被移除 在关闭Broker时,它对应的临时节点也会消失,不过它的ID会继续存在于其他数据结构中 例如,主题的副本列表里可能会包含这些ID 在完全关闭了一个Broker之后,如果使用相同的ID启动另一个全新的Broker 该Broker会立即加入集群,并拥有与旧Broker...
Kafka -- Docker + Schema Registry
Avro Avro的数据文件里包含了整个Schema 如果每条Kafka记录都嵌入了Schema,会让记录的大小成倍地增加 在读取记录时,仍然需要读到整个Schema,所以需要先找到Schema 可以采用通用的结构模式并使用Schema注册表的方案 开源的Schema注册表实现:Confluent Schema Registry Confluent Schema Registry 把所有写入数据需要用到的Schema保存在注册表里,然后在_记录里引用Schema ID_ 负责读数据的应用程序使用Schema ID从注册表拉取Schema来反序列化记录 序列化器和反序列化器分别负责处理Schema的注册和拉取 Confluent Schema Registry1234567891011121314151617181920# Start Zookeeper and expose port 2181 for use by the host machine$ docker run -d --name zookeeper -p 2181:2181 confluent/zookeeper# Star...
MySQL -- 自增ID耗尽
显示定义ID表定义的自增值ID达到上限后,在申请下一个ID时,得到的值保持不变 123456789101112131415161718-- (2^32-1) = 4,294,967,295-- 建议使用 BIGINT UNSIGNEDCREATE TABLE t (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY) AUTO_INCREMENT=4294967295;INSERT INTO t VALUES (null);-- AUTO_INCREMENT没有改变mysql> SHOW CREATE TABLE t;+-------+------------------------------------------------------+| Table | Create Table |+-------+------------------------------------------------------+| t | CREATE TABLE `t` ( `id` ...
MySQL -- 分区表
表初始化1234567891011121314CREATE TABLE `t` ( `ftime` DATETIME NOT NULL, `c` int(11) DEFAULT NULL, KEY (`ftime`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 PARTITION BY RANGE (YEAR(ftime)) (PARTITION p_2017 VALUES LESS THAN (2017) ENGINE = InnoDB, PARTITION p_2018 VALUES LESS THAN (2018) ENGINE = InnoDB, PARTITION p_2019 VALUES LESS THAN (2019) ENGINE = InnoDB, PARTITION p_others VALUES LESS THAN MAXVALUE ENGINE = InnoDB);INSERT INTO t VALUES ('2017-4-1',1),('2018-4-1',1);...













