1. 线程安全性什么是线程安全性?我们可以这么理解,我们所写的代码在并发情况下使用时,总是能表现出正确的行为;反之,未实现线程安全的代码,表现的行为是不可预知的,有可能正确,而绝大多数的情况下是错误的。正如 Java 语言规范在《Chapter 17. Threads and Locks》所说的:
线程的行为(尤其是在未正确同步的情况下)可能会造成混淆并且违反直觉。本章描述了多线程程序的语义。它包括规则,通过读取多个线程更新的共享内存可以看到值。
如果要实现线程安全性,就要保证我们的类是线程安全的。在《Java 并发编程实战》中,定义“类是线程安全的”如下:
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在调用代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
如何实现线程安全?线程封闭实现好的并发是一件困难的事情,所以很多时候我们都想躲避并发。避免并发最简单的方法就是线程封闭。
线程封闭就是把对象封装到一个线程里,只有这一个线程能看到此对象。那么这个对象就算不是线程安全的也不会出现任何安全问题。 ...
1. 什么是原子操作?如何实现原子操作?事务的一大特性就是原子性(事务具有 ACID 四大特性)。一个事务包含多个操作,这些操作要么全部执行,要么全都不执行。
并发中的原子性和原子操作具有相同的内涵和概念。假定有两个操作 A 和 B 都包含多个步骤,如果从执行 A 的线程来看,当另一个线程执行 B 时,要么将 B 全部执行完,要么完全不执行 B。执行 B 的线程看 A 的操作也是一样的,那么 A 和 B 对彼此来说是原子的。
如何实现原子操作?1. 使用锁机制锁机制可以满足基本的需求,但是存在一些问题:
阻塞问题:当一个线程拥有锁时,访问同一资源的其它线程需要等待,直到该线程释放锁。
优先级反转:如果被阻塞的线程优先级很高会影响系统的整体性能。
死锁风险:如果获得锁的线程一直不释放锁,可能会造成死锁。
粒度较大:对于某些精细的需求,如计数器操作,锁机制可能显得笨重。
详细解释
实现原子操作可以使用锁,锁机制,满足基本的需求是没有问题的了,但是有的时候我们的需求并非这么简单,我们需要更有效,更加灵活的机制,synchron ...
1. ThreadLocal 介绍ThreadLocal的定义如下:
此类提供线程局部变量。这些变量与普通变量的不同之处在于,每个线程访问一个变量时(通过get或set方法)都有自己独立初始化的变量副本。ThreadLocal实例通常是希望将状态与线程(例如,用户ID或事务ID)相关联的类中的私有静态字段。也就是说 ThreadLocal 为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享
ThreadLocal 和 Synchronized 的区别
ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,从而隔离了多个线程对数据的共享访问。
Synchronized是通过锁的机制,使变量或代码块在某一时间只能被一个线程访问,而ThreadLocal则是副本机制。
ThreadLocal 的使用ThreadLocal类接口很简单,只有4个方法:
void set(Object value): 设置当前线程的线程局部变量的值。
public Object get(): 获取当前线程 ...
1. 基础概念在正式学习 Java 的并发编程之前,还有几个并发编程的基础概念我们需要熟悉和学习。
进程和线程进程我们常听说的是应用程序,也就是 app,由指令和数据组成。但是当我们不运行一个具体的 app 时,这些应用程序就是放在磁盘(也包括 U 盘、远程网络存储等等)上的一些二进制的代码。一旦我们运行这些应用程序,指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备,从这种角度来说,进程就是用来加载指令、管理内存、管理 IO 的。
当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
进程可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器 等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)。显然,程序是死的、静态的,进程是活的、动态的。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身,用户进程就是所有由你启动的进程。
站在操作系统的角度,进程是程序运行资源分配(以内存为主)的最小 ...
1. 生成短链接1234567891011121314151617private String generateSuffix(ShortLinkCreateReqDTO requestParam) { int customGenerateCount = 0; String shorUri; String originUrl = requestParam.getOriginUrl(); while (true) { if (customGenerateCount > 10) { throw new ServiceException("短链接频繁生成,请稍后再试"); } shorUri = HashUtil.hashToBase62(originUrl); if (!shortUriCreateCachePenetrationBloomFilter.contains(createShortLinkDefaultDomain ...
1. 创建短链接分组用户创建分组限制最大数量由于短链接项目中,用户可以创建多个分组,但是每个用户最多只能创建 groupMaxNum 个分组,这里的 groupMaxNum 为 20。
每次新建分组前都需要查询数据库,获取当前用户已经创建的分组数,如果已经超出最大分组数,则抛出异常。
而并发场景下,为了防止同一用户在不同线程(可能同一用户在多个设备)同时新增分组,而超出最大分组数。这里使用了 Redisson 的分布式锁,锁的 key 为 LOCK_GROUP_CREATE_KEY + 用户名,这样就能保证同一用户在不同线程同时新增分组时,只有一个线程能获取到锁,其他线程会阻塞等待。
在获取到锁后,再次查询数据库,判断当前用户是否已经超出最大分组数,如果没有超出,则生成唯一的分组标识 gid,并插入数据库。
123456789101112131415161718192021222324252627282930313233343536public void saveGroup(String username, String groupName) { RLock l ...
前提:数据库用户表分表用户名全局唯一
分片键为用户名登录时需要通过用户名查询用户信息,所以需要将用户表分片,以用户名为分片键,将用户名的hash值对分片数取模。而如果用用户ID作为分片键,那么在通过用户名登录时查询没有带上分片键,数据库会用union all的形式查所有的表,造成非常大的性能深渊。
分片键的关键因素:访问频率:选择分片键应考虑数据访问频率。将经常访问的数据放在一个分片上,可以提高查询性能和降低跨分片查询的开销。数据均匀性:分片键应该保证数据的均匀分布在各个分片上,避免出现热点数据集中在某个分片上的情况。数据不可变:一旦选择了分片键,它应该是不可变的,不能随着业务的变化而频繁修改。
业务流程
完整代码
12345678910111213141516171819202122232425262728@Overridepublic Boolean hasUsername(String username) { return !userRegisterCachePenetrationBloomFilter.contains(username);}@Tr ...
Java中的回调函数(Callback Function)通常是指一种通过传递函数引用或对象方法来实现延迟执行的机制。回调函数通常是在一个方法执行完成后,由另一个方法来调用。
在Java中,回调函数的实现通常有以下几种方式:
1. 使用接口实现回调Java中回调函数通常通过定义接口,然后将实现该接口的对象传递给其他类来实现。
示例:1234567891011121314151617181920212223242526272829303132333435363738// 定义回调接口interface Callback { void onCallback(String result);}// 被回调的类class Task { private Callback callback; // 构造方法传入回调对象 public Task(Callback callback) { this.callback = callback; } // 执行任务并回调 public void execu ...
1. MySQL支持的存储引擎MySQL提供了多种存储引擎,包括:
InnoDB(默认存储引擎,支持事务、行级锁、外键)
MyISAM(不支持事务,但查询速度快)
Memory(数据存储在内存中,适用于临时表)
Archive(适用于日志存储,支持高压缩)
CSV(数据以CSV格式存储)
BLACKHOLE(写入的数据被丢弃)
FEDERATED(可访问远程MySQL数据库)
ndbcluster(用于MySQL Cluster)
查看当前MySQL版本及存储引擎支持情况12SELECT VERSION();SHOW ENGINES;
2. MySQL的SQL执行流程
执行SQL的流程涉及多个模块:
客户端连接器(Client Connectors):JDBC、ODBC、PHP等
连接池(Connection Pool):管理数据库连接
SQL解析(Parser):进行词法分析、语法分析
优化器(Optimizer):优化SQL查询的执行方式
查询缓存(Caches):缓存查询结果
存储引擎(Pluggable Storage Engines):如InnoDB、MyISAM等 ...
1. 二叉树(Binary Tree)特点
每个节点最多有两个子节点(左子节点和右子节点)。
可能是 平衡的 或 不平衡的,这直接影响查找复杂度。
时间复杂度
最坏情况(树退化成链表):O(𝑛)
平均情况(随机插入的情况下):O(√𝑛)
最优情况(平衡二叉树):O(log 𝑛)
分析
如果二叉树是 满二叉树,树的高度 h = log₂(𝑛),查找复杂度 O(log 𝑛)。
如果二叉树是 退化树(每个节点只有一个子节点),树的高度 h = 𝑛,查找复杂度 O(𝑛)。
2. 二叉搜索树(BST - Binary Search Tree)特点
满足 左子树的值小于根节点,右子树的值大于根节点。
适合 快速查找、插入、删除,但 树的形态决定了性能。
时间复杂度
最坏情况(退化成链表):O(𝑛)
平均情况(随机插入的情况下):O(log 𝑛)
最优情况(完全平衡 BST):O(log 𝑛)
分析
平衡 BST:类似满二叉树,查找复杂度 O(log 𝑛)。
非平衡 BST(退化成链表):O(𝑛),例如,插入 1 → 2 → 3 → 4 → 5 ...