Redis设计与实现——简单动态字符串SDS
Redis 没有直接使用 C 语言传统的字符串表示(以空字符\0结尾的char类型字符数组,以下简称 C 字符串), 而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型, 并将 SDS 用作 Redis 的默认字符串表示。
在 Redis 里面, C 字符串只会作为字符串字面量(string literal), 用在一些无须对字符串值进行修改的地方,比如打印日志。
SDS 的定义每个 sds.h / sdshdr 结构表示一个 SDS 值,在 Redis 3.2 版本以前,SDS 的结构如下:
1234567891011struct sdshdr { // 记录 buf 数组中已使用字节的数量 // 等于 SDS 所保存字符串的长度 unsigned int len; // 记录 buf 数组中剩余可用字节数量 unsigned int free; // 字节数组,用于保存字符串 char buf[];};
free 属性的值为 0 , 表示这个 SDS ...
synchronized底层原理与锁升级
synchronized 关键字解决的是多个线程之间访问资源的同步性,即保证线程同步,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中,synchronized 属于 重量级锁,效率低下。为什么呢?
因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。
在 Java 6 之后 Java 官方对从 JVM 层面对 synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
使用synchronized 实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下 3 种形式:
修饰实例方法,锁是当 ...
深入理解volatile底层原理
volatile 是轻量级的 synchronized,一般作用于变量。相比于synchronized关键字,volatile关键字的执行成本更低,因为它不会引起线程上下文的切换和调度。
volatile主要有以下两个功能:
保证共享变量的内存可见性(即当一个线程修改一个共享变量时,另一个线程能读到这个修改的值)。
禁止指令重排序
volatile 的用途:
从volatile的内存语义上来看,volatile可以保证内存可见性且禁止重排序。
在保证内存可见性这一点上,volatile有着与锁相同的内存语义,所以可以作为一个“轻量级”的锁来使用。但由于volatile仅仅保证对单个volatile变量的读/写具有原子性,而锁可以保证整个临界区代码的执行具有原子性。所以在功能上,锁比volatile更强大;在性能上,volatile更有优势。
volatile的特性volatile可以保证可见性和有序性。
可见性:volatile可以保证不同线程对共享变量进行操作时的可见性。即当一个线程修改了共享变量时,另一个线程可以读取到共享变量被修改后的值。
有序性:volatile会 ...
并发编程的艺术——Java内存模型(指令重排序、happens-before)
什么是 Java 内存模型Java 内存模型Java Memory Model是一种抽象的概念,并不真实存在,它描述了一组规则或规范,通过这组规范定义了程序中共享变量(包括实例字段、静态字段和数组元素)的访问方式。在 Java 中,所有实例字段、静态字段和数组元素(称为共享变量)都存储在堆内存中,堆内存在线程之间共享。栈中的变量(局部变量、方法定义参数、异常处理器参数)不会在线程之间共享,也就不会有内存可见性的问题,也不受内存模型的影响。
Java 线程之间的通信由 Java 内存模型(JMM)控制,JMM 决定了一个线程对共享变量的写入何时对另一个线程可见。
Java 内存模型的组成部分JMM 定义了线程和主内存之间的抽象关系:
线程之间的共享变量存储在主内存(Main Memoery)中,每个线程都有一个私有的本地内存(Local Memoery),存储了该线程以读 / 写共享变量的副本。线程对共享变量的所有操作都必须在本地内存中进行,而不能直接对主内存进行操作。并且每个线程不能访问其他线程的工作内存。JMM 通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证。
...
AQS原理与ReentrantLock源码分析
AQS 的全称为 AbstractQueuedSynchronizer,即抽象队列同步器,这个类在java.util.concurrent.locks包下面。
AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出大量应用广泛的同步器,比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask 等等皆是基于 AQS 的。
AQS 原理AQS 核心思想 / 工作流程是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(No ...
ThreadLocal原理以及内存泄漏问题
什么是ThreadLocal?有哪些应用场景?ThreadLocal类可以让每个线程绑定自己的值,也就是拥有自己的专属本地变量。
ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,并且不会和其他线程的变量冲突,实现了线程间的数据隔离,避免了线程安全问题。
ThreadLocal的应用场景主要有以下几个方面:
保存线程上下文信息,避免参数的显示传递,在需要的地方可以直接获取
线程间数据隔离
进行事务操作时存储线程事务信息,因为事务和线程绑定在一起(Spring在事务开始时会给当前线程绑定一个Jdbc Connection对象,放在ThreadLocal中存储,这样在整个事务执行过程中都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性)
数据库连接(经典的使用场景是为每个线程分配一个JDBC Connection连接对象,这样可以保证每个线程的都在各自的Connection上进行数据库的操作,不会出现A线程关了B线程正在使用的Connection)
session会话等线程级别的操作(Session 的特性很适合 ...
分布式基础——几种常见的分布式锁
分布式锁在单机场景下,可以使用语言的内置锁来实现进程同步,如 synchronized、Lock等。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。
分布式锁:是控制分布式系统不同进程共同访问共享资源的一种锁的实现。
数据库的唯一索引向表中插入一条唯一索引的记录,此时相当于加锁,释放锁时删除这条记录。唯一索引可以保证该记录只被插入一次,那么就可以用这个记录是否存在来判断是否处于锁定状态。
存在以下几个问题:
锁没有失效时间,解锁失败的话其它进程无法再获得该锁;
只能是非阻塞锁,插入失败直接就报错了,无法重试;
不可重入,已经获得锁的进程也必须重新获取锁。
Redis 的 SETNX 指令使用 SETNX(set if not exist)指令插入一个键值对,如果 Key 已经存在,那么会返回 False,否则插入成功并返回 True。
SETNX 指令和数据库的唯一索引类似,保证了只存在一个 Key 的键值对,那么可以用一个 Key 的键值对是否存在来判断是否存于锁定状态。
EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了数据库唯一索引 ...
分布式基础——CAP理论与BASE理论
本文内容整理自:https://blog.csdn.net/qq_34337272/article/details/80444032,并附上自己的理解
CAP理论简介CAP 也就是 Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性) 这三个单词首字母组合。
在理论计算机科学中,CAP 定理(CAP theorem)指出对于一个分布式系统来说,当设计读写操作时,只能同时满足以下三点中的两个:
一致性(Consistency) : 所有节点访问同一份最新的数据副本
可用性(Availability): 非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。
分区容错性(Partition tolerance) : 分布式系统出现网络分区的时候,仍然能够对外提供服务。
什么是网络分区?
分布式系统中,多个节点之前的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)某些节点之间不连通了,整个网络就分成了几块区域,这就叫网络分区。
权衡当发生网络分区的时候,如果我们要继续服务,那么强一 ...
Java线程池实现原理与ThreadPoolExector详解
线程池(Thread Pool)是一种基于池化思想管理线程的工具。
为什么要使用线程池?
降低资源消耗:通过池化技术重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
线程池参数核心构造方法如下:
1234567public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, ...
Java中的阻塞队列BlockingQueue
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。
支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满,如 put() 方法。
支持阻塞的移除方法:在队列为空时,获取元素的线程会等待队列变为非空,如 take() 方法。
BlockingQueue 常用于生产者-消费者场景,生产者是往队列里添加元素的线程,消费者是从队列里取元素的线程。BlockingQueue就是存放元素的容器。
阻塞队列提供了四组不同的方法用于插入、移除、检查元素:
方法/处理方式
抛出异常
返回特殊值
一直阻塞
超时退出
插入方法
add(e)
offer(e)
put(e)
offer(e,time,unit)
移除方法
remove()
poll()
take()
poll(time,unit)
检查方法
element()
peek()
-
-
抛出异常:如果试图的操作无法立即执行则抛出异常。当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue ful ...