朋友圈点赞功能如何实现,简单说说?首先我们要理清朋友圈点赞具体需要涉及哪几个功能点:
1)存储点赞信息
需要存储哪些用户点赞了这条朋友圈,具体需要存储用户ID、点赞时间即可。
2)取消点赞
需要快速找到这名用户,将其移除点赞列表。
3)获取点赞列表
朋友圈需要展示点赞的用户头像列表信息.
核心就是这么三点,其实就是增删查,那用什么来实现比较合适呢?
实现快速存储和删除,Set 就挺合适,而且...
让你实现一个分布式单例对象,如何实现?所谓的单例一般指的是一个进程中一个类对应只有一个实例对象,也就是进程唯一。
而分布式,不过是一个机器部署多个服务,还是多个机器部署,本质上就是多进程,所以所谓的分布式单例指的是这个实例对应需要在多进程中保持唯一。
按照这个思路,我们仅需控制同一时刻,只会有一个进程使用这个单例对象即可,而分布式场景下的分布式锁就很容易实现这个功能。
多个进程竞争分布式锁,...
如果让你统计每个接口每分钟调用次数怎么统计?最简单的可以使用 ConcurrentHashMap + AtomicInteger + 定时任务实现内存中的统计。
ConcurrentHashMap 的 key 为方法的名称、value 为 AtomicInteger 类型,记录调用次数,可以通过 aop 切面实现每个方法调用都记录到 ConcurrentHashMap 中,然后利用定时任务每...
分布式锁一般都怎样实现?分布式锁需要实现多个应用实例之间的临界资源竞争,因此它需要依赖三方组件才能实现这样的功能。
常见依赖 Redis、ZooKeeper 来实现分布式锁。
Redis 实现基于缓存实现分布式锁性能上会有优势,可以使用 Redis SETNX (SET if Not eXists)实现分布式锁。
注意锁需要设置过期时间,防止应用程序崩溃导致锁没有释放而阻塞后面的所有操作。
...
HashMap 是不是线程安全的?如果让你来实现一个线程安全的 HashMap 你要怎么设计?如果不用加锁你要怎么设计?回答重点HashMap 是 非线程安全 的。因为 HashMap 的内部实现并没有加锁,多个线程同时访问和修改时可能会引发数据竞争,导致数据不一致或陷入死循环等问题。
如何实现一个线程安全的 HashMap要实现一个线程安全的 HashMap,有多种设计方案,下面是几种常见...
什么情况下需要使用分布式事务,有哪些方案?回答重点一般在跨多个数据库、或者不同服务的情况下需要用到分布式事务,比如订单服务和库存服务,下订单和扣库存属于不同服务的方法,因此本地事务无法保证一致性,需要引入分布式服务。
分布式事务是由多个本地事务组成的,分布式事务跨越了多设备,之间又经历的复杂的网络,可想而知想要实现严格的事务道路阻且长。
常见的分布式事务方案:
2PC:基于两阶段提交(2P...
Seata 支持哪些模式的分布式事务?回答重点Seata 目前支持四种事务模式,分别是 AT、TCC、Saga 以及 XA 模式:
AT 模式:通过代理数据库操作来实现分布式事务管理。Seata 在业务操作前后自动生成回滚日志,在提交时直接提交本地事务,在回滚时利用日志进行数据的还原。
TCC 模式:是一种两阶段提交。将一个操作拆分为三个步骤:Try(预留资源)、Confirm(确认操作)...
Dubbo 和 Spring Cloud Gateway 有什么区别?回答重点Dubbo 是一个 RPC(远程过程调用)框架,主要用于服务之间的通信。它提供高性能的 RPC 调用、负载均衡、服务发现、服务注册、服务治理等功能。
适用于需要高性能 RPC 调用的分布式系统,常用于内部服务通信。
Spring Cloud Gateway 是一个 API 网关,用于处理外部客户端请求并将其路由到后...
Go 语言使用断言时会发生拷贝吗?回答重点在 Go 语言中,类型断言是否发生拷贝取决于接口内部持有的数据类型:
值类型:当接口持有的是值类型(例如 int、float、struct 等),进行类型断言时会发生拷贝,因为接口存储的是这个值的副本,断言后得到的是该值的拷贝。
引用类型:当接口持有的是引用类型(例如指针、切片、映射、通道等),进行类型断言时不会发生拷贝,因为接口存储的是一个引用,...
Go 语言中如何访问私有成员?重点回答在 Go 语言中,以小写字母开头的标识符是私有成员,私有成员(字段、方法、函数等)遵循语言的可见性规则,仅在定义它的包内可见,包外无法访问这些私有成员。如果想要访问私有成员,主要包括以下三种方式:
在同一个包内,可以直接访问小写字母开头的私有成员。
在其他包中,无法直接访问私有成员,但可以通过公开的接口来间接访问私有成员。
使用反射来绕过 Go 语言的...
Go 语言的接口是怎么实现的?回答重点在 Go 语言中,接口(interface)是一种动态类型,允许定义对象的行为,而不需要指定具体的实现。
它本质上是一个动态类型和动态值的组合:
动态类型:接口持有的具体数据的类型。
动态值:接口持有的具体数据的值或引用。
接口通过这两部分,实现对不同类型的统一操作。
Go 采用鸭子类型的设计哲学,不需要显式声明实现关系。只要一个类型的方法集满足接口...
Go 语言中怎么实现闭包?闭包的主要应用场景是什么?回答重点在 Go 语言中,闭包(Closure)是一个函数值,它可以引用其外部作用域中的变量。在 Go 中实现闭包的方法非常简单,我们可以通过在一个函数内部定义另一个函数,并让其访问外部函数的变量来实现。
即函数可以访问被引用的变量并对其赋值,函数被“绑定”到变量上。
下面是一个简单的 Go 语言闭包示例:
12345678910111...
Go 语言中触发异常的场景有哪些?重点回答在 Go 语言中,使用 error 类型来处理错误,并通过 panic 和 recover 来处理程序的异常情况。以下是一些可能触发 panic(即异常)的场景:
数组或切片越界
空指针解引用
调用 panic 函数
非法类型断言
数学错误
内存越界或非法操作
运行时错误
使用不安全的库或代码
在上述1、2、4和5是在写代码中最常遇见的异常场景。...
Go 语言中 defer 的变量快照在什么情况下会失效?重点回答在 Go 语言中,defer 的变量快照是指在 defer 语句定义时所捕获的变量的状态。但有些情况下,defer 语句中的变量快照可能会失效,导致不如预期那样行为,如下:
1)匿名函数闭包:当 defer 语句中使用的匿名函数捕获了外部变量时。如果变量的值在 defer 语句定义后发生变化,defer 执行时会使用变化后的值。...
不分配内存的指针类型能在 Go 语言中使用吗?回答重点在 Go 语言中,不分配内存的指针类型可以使用,但是只能用该指针本身,不可以用*去解引用出具体的值,会导致 panic 。这是因为 Go 允许声明指针变量,但如果不分配内存(没有指向有效的地址),该指针会是 nil。访问 nil 指针会导致运行时错误。
简单的说,Go 中声明一个指针变量是非常直接的,你可以使用 *Type 来声明一个指针...
Go 语言中通过指针变量 p 访问其成员变量 title,有哪几种方式?回答重点在 Go 语言中,通过指针变量 p 访问其成员变量 title 主要有以下两种方式:
1)使用 (*p).title 访问成员变量。
2)由于 Go 提供了指针的简写支持,还可以直接使用 p.title 来访问成员变量。
这两种方式其实是等价的,Go 编译器会帮你处理其中的细节。
有 4 种情况可以使用简洁支持:...
Go 语言中所有的 T 类型都有 *T 类型吗?重点回答不是。在 Go 语言中,几乎所有的类型 T 都可以有一个对应的指针类型 *T,不过接口类型的指针是无效的。
扩展知识1)普通情况对于大多数类型(包括基础类型、自定义类型、结构体、切片、映射、通道等),你可以使用 *T 来表示类型 T 的指针。以下是一些示例:
基础类型:
12var a int = 10var p *int = &...
Go 语言中 init() 函数在什么时候执行?回答重点init() 函数在 Go 程序执行之前自动调用,会在 main() 函数执行之前。
它用于初始化包级别的变量,用来设置初始状态或者执行一次性初始化操作(它不能有参数,也不能返回值)。每个包中的 init() 函数在该包的其他代码执行之前运行,每个包可以有多个 init() 函数。
执行顺序:
包的初始化顺序:如果一个包被多个包依赖,...
Go 语言中的局部变量是分配在栈上还是堆上?回答重点Go 语言中的局部变量既可能分配在栈上,也可能分配在堆上
如果变量的生命周期局限于函数作用域,并且不会逃逸到函数外,则分配在栈上。
如果局部变量的生命周期超出函数作用域(如通过指针返回给外部使用),编译器会将变量分配在堆上,确保变量在作用域外仍然有效,这种机制称为“逃逸分析”。
扩展知识栈和堆的区别栈分配:
栈是线程私有的,分配和释放内存...
GO语言中非接口的任意类型T都能调用*T的方法么?反过来呢?回答重点都可以的。在Go语言中,对于非接口的任意类型T,确实可以调用 * T(指向T的指针)的方法。这是因为当你尝试在一个T类型的值上调用一个 * T 方法时,Go编译器会隐式地获取该值的地址,然后调用相应的方法。这种行为被称为指针接收者的方法调用的自动解引用。
示例代码1234567891011121314151617181920...
Go 语言切片的容量是如何增长的?回答重点在 Go 语言中,切片的容量是一种动态增长的机制。当切片的长度达到或超过容量时,Go 语言会自动扩展其底层数组的容量,一般由append触发。切片容量增长(growslice)的具体规则在不同版本的规则不同。
对于 go1.18 之前来说:
如果期望容量大于当前容量的两倍就会使用期望容量;
如果当前切片的长度小于 1024 的话, growslic...
Java 创建线程池有哪些方式?回答重点1)使用 Executors 工厂类,例如Executors.newFixedThreadPool(10);
2)使用 ThreadPoolExecutor 直接创建线程池
1234567ExecutorService threadPool = new ThreadPoolExecutor( 5, // corePoolSize 10, /...
Java 线程安全的集合有哪些?回答重点
类名
描述
线程安全模型
使用场景
Vector
线程安全的动态数组
每个方法加锁
较低并发需求的场景(不推荐使用)
Hashtable
线程安全的哈希表
每个方法加锁
较低并发需求的场景(不推荐使用)
ConcurrentHashMap
线程安全的哈希表(高并发)
分段锁,支持高并发
高并发的场景,如缓存、分布式锁等
Copy...
线程的生命周期在 Java 中是如何定义的?回答重点在 Java 中,线程的生命周期可以细化为以下几个状态:
New(初始状态):线程对象创建后,但未调用 start() 方法。
Runnable(可运行状态):调用 start() 方法后,线程进入就绪状态,等待 CPU 调度。
Blocked(阻塞状态):线程试图获取一个对象锁而被阻塞。
Waiting(等待状态):线程进入等待状态,需...
Java 中的线程安全是什么意思?回答重点线程安全是指多个线程访问某一共享资源时,能够保证一致性和正确性,即无论线程如何交替执行,程序都能够产生预期的结果,且不会出现数据竞争或内存冲突。在 Java 中,线程安全的实现通常依赖于同步机制和线程隔离技术。
扩展知识常用的线程安全措施
同步锁:通过 synchronized 关键字或 ReentrantLock 实现对共享资源的同步控制。
原子操...
你了解 Java 线程池的原理吗?回答重点线程池是一种池化技术,用于预先创建并管理一组线程,避免频繁创建和销毁线程的开销,提高性能和响应速度。
它几个关键的配置包括:核心线程数、最大线程数、空闲存活时间、工作队列、拒绝策略。
主要工作原理如下:
默认情况下线程不会预创建,任务提交之后才会创建线程(不过设置 prestartAllCoreThreads 可以预创建核心线程)。
当核心...
Java 中线程之间如何进行通信?回答重点在 Java 中,线程之间的通信是指多个线程协同工作,主要实现方式包括:
1)共享变量:
线程可以通过访问共享内存变量来交换信息(需要注意同步问题,防止数据竞争和不一致)。
共享的也可以是文件,例如写入同一个文件来进行通信。
2)同步机制:
synchronized:Java 中的同步关键字,用于确保同一时刻只有一个线程可以访问共享资源,利用 ...
如何合理地设置 Java 线程池的线程数?回答重点线程池的线程数设置需要看具体执行的任务是什么类型的。
任务类型可以分:CPU 密集型任务和 I/O 密集型任务。
CPU 密集型任务CPU 密集型任务,就好比单纯的数学计算任务,它不会涉及 I/O 操作,也就是说它可以充分利用 CPU 资源(如果涉及 I/O,在进行 I/O 的时候 CPU 是空闲的),...
Java 并发库中提供了哪些线程池实现?它们有什么区别?回答重点Java 并发库中提供了 5 种常见的线程池实现,主要通过 Executors 工具类来创建。
1)FixedThreadPool:创建一个固定数量的线程池。
线程池中的线程数是固定的,空闲的线程会被复用。如果所有线程都在忙,则新任务会放入队列中等待。
适合负载稳定的场景,任务数量确定且不需要动态调整线程数。
2)CachedT...
Java 中的 DelayQueue 和 ScheduledThreadPool 有什么区别?回答重点DelayQueue 是一个阻塞队列,而 ScheduledThreadPool 是线程池,不过内部核心原理都是差不多的。
DelayQueue 是利用优先队列存储元素,当从队列中获取任务的时候,如果最老的任务已经到了执行时间,可以从队列中出队一个任务,反之可以获得 null 或者阻塞等待任...