- 博客(67)
- 收藏
- 关注
原创 内存映射mmap
1、什么是内存映射内存映射,准确来说是指内存映射文件,即把磁盘上的文件映射到进程的虚拟地址空间中(在磁盘上)。当进程需要访问这段虚拟地址时,发生缺页中断,这样就会把页面swap进物理内存。这其中只发生了一次DMA拷贝,即把页面从磁盘拷贝到内核空间的页缓存 (page cache),然后将用户空间的虚拟地址映射到内核的page cache,这样不需要再将页面从内核空间拷贝到用户空间了。如果进程改变了页面的内容,一段时间后,OS会把脏页面写回到磁盘。2、内存映射的优点内存映射可以只加载文件的一部分内容
2021-08-27 11:06:05
265
原创 如何理解HTTP2.0的升级
本文根据个人理解描述了 HTTP2.0 的几个重大更新,并非全部更新。1、二进制分帧层首先,在应用层和传输层之间新增了一个层——二进制分帧层。在二进制分帧层,HTTP消息被分解为更小的、互不依赖的帧,然后乱序发送。最后在接收端进行重新组装。在HTTP1.x中,如果想要并发进行多个HTTP请求,就需要依赖多个TCP连接来实现,然而HTTP1.x中对单个域名的TCP连接是有限制的。而HTTP2.0有了二进制分帧后,HTTP2.0不再依赖多个TCP连接去并发多个请求了。同域名下所有通信都在单个TCP连接上
2021-08-22 19:07:50
264
原创 分布式锁的三种实现方式
1. 使用DB实现①悲观锁多个线程并发执行select for update,只有一个线程都成功获取到锁。当这个线程执行commit操作后,才能释放锁。问题:连接池爆满(加锁的时候,select会占用数据库连接,获取不到行锁的时候连接也不释放,就会导致连接池爆满)。事务超时(事务处理过长,到了超时时间就会自动回滚,导致锁释放,这个时间要把控好)。行锁升级为表锁(InnoDb的行锁是加在索引上的,如果不走索引就会变成表锁,所以必须使用索引字段)。②乐观锁乐观锁使用一个version字段来表示当前行的版
2021-08-06 17:20:20
230
原创 IO多路复用的三种机制Select、Poll、Epoll
select、poll和epoll都是IO多路复用机制,可以监视多个文件描述符fd(每个fd关联了对应的socket),一旦某个fd有事件发生,就能够通知程序进行相应的IO操作。(select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。)epoll是linux2.6以后Linux特有的实现机制,select则是一般OS都有的实现机制。1、sele
2021-05-08 18:00:48
247
原创 算法学习(二)
本文介绍求解动态规划问题的一种轨迹:从暴力递归到递归优化,再到动态规划。本文将使用两个典型案例来描述这种轨迹,文章比较长,有兴趣者可以阅读。【例题1】表达式字符串中只有0(假)、1(真)、&(与)、|(或)和^(异或)这五个符号,给定一个bool类型期望值desired,对表达式字符串进行组合,求共有多少组合方式,使得组合的结果是desired。比如,表达式字符串为 1 ^ 0 | 0 |1,desired为false,共有2各种组合方式分别是1 ^ ( ( 0 | 0 ) | 1 )和1 ^ (
2021-04-05 22:49:17
318
2
原创 算法学习(一)
【题目】求数组中的最长连续序列的长度,序列中每个值的索引不要求连续,但值时连续的。【例如】数组data={100, 1, 200, 3, 2, 4},最长连续序列为{1, 2, 3, 4},返回4。【所属范围】动态规划【解】本题使用一个HashMap存储中间状态,key为数组元素值,value为该值所在的连续序列的长度。遍历数组时,利用HashMap可以判断当前元素 data[i] 是否已经出现过,如果出现过了,就不作处理,因为它不会影响已有的连续序列的发生扩张。如果没有出现过,就把它当做是长度为1的
2021-04-04 21:30:16
125
原创 JDK源码阅读(十四) : 优先级队列——PriorityQueue
1. 优先级队列PriorityQueue是一种基于堆的无界优先级队列。内部使用Object数组存储数据,在容量不足时会进行扩容操作。内部元素的排序规则,按照构造实例时传入的Comparator或者元素自身的排序规则(所属类实现Comparable接口)。2. Fields默认的数组长度为11。private static final int DEFAULT_INITIAL_CAPACITY = 11;用于存储数据的Object数组。transient Object[] queue;元素数
2021-01-09 16:21:20
223
原创 并发:读写锁ReentrantReadWriteLock和StampedLock
1. 读写锁读写锁通常遵守以下三条规则:多个线程可以同时读共享变量;只允许一个线程写共享变量;对共享变量写的时候,不允许对该变量读。简单来说,读写锁允许同时读,读和写互斥,写和写互斥。因此,在读多写少的场景下,读写锁比普通的互斥锁更具优势。2. 读写锁ReentrantReadWriteLockReentrantReadWriteLock从字面上来看,可以知道它是一个可重入的读写锁。在使用容器时,使用ReentrantReadWriteLock,可以实现较好的并发性能,尤其是在读多写少
2021-01-05 16:18:01
325
原创 并发:java内存模型
1. 前言可见性、有序性和原子性导致了并发编程的各种问题。导致可见性的原因是缓存,导致有序性的原因是编译优化。解决这两个问题的直接方法就是按需禁用缓存和编译优化。java内存模型规范了JVM如何提供按需禁用内存和编译优化的方法。这些技术主要包含volatile关键字、Happens-Before规则和final关键字。2. volatilevolatile关键字用于修饰一个变量,作用就是禁用缓存。当一个变量被volatile修饰,就是要告诉编译器,只能从内存中读取该变量,也只能写入到内存中。3
2021-01-04 21:06:09
111
原创 JDK源码阅读(十三) : 单端阻塞队列——ArrayBlockingQueue
1. 队列队列是一种先进先出(First In First Out)的数据结构。可以分成阻塞和非阻塞两类:阻塞队列,即如果队列已满,入队操作会阻塞;如果队列为空,出队操作也会阻塞。(Blocking标识)非阻塞队列,即如果队列已满,执行入队操作会直接返回;如果队列为空,执行出队操作也是直接返回。也可以分成单端和双端两类:单端队列,即只能在队尾入队,队首出队;(Queue标识)双端队列,队首队尾都可以出队。(Deque标识)单端阻塞队列:主要有ArrayBlockingQueue(内
2020-12-29 21:28:19
203
原创 JDK源码阅读(十二) : 基于跳表的并发容器——ConcurrentSkipListMap
1. 简介ConcurrentSkipListMap是有序的hash表,是线程安全的。与之对比的另外两种hash容器,ConcurrentHashMap虽然是线程安全的,但是key并不是有序的;而TreeMap虽然key是有序的,但是不是线程安全的。ConcurrentSkipListMap采用无锁方案,支持更高的并发,存取时间是O(logN),与线程数无关。也就是说,当数据量一定的情况下,并发线程数越多,ConcurrentSkipListMap优势越大。2. 数据结构ConcurrentSki
2020-12-29 16:38:25
322
1
原创 JDK源码阅读(十一) : 并发容器——ConcurrentHashMap
1. Why ConcurrentHashMap?在java1.7版本,数组中发生冲突的节点以链表形式相连,在进行HashMap在执行put方法时,通过头插法插入到单链表中。然而put过程中可能会触发扩容操作,此时会将原数组的内容重新散列(rehash)到新的数组中。在多线程环境下,如果多个线程同时执行put方法,可能会使数组某一位置上的链表形成闭环,继而出现死循环,cpu飙升到100%。所以java1.7中的hashmap不是线程安全的。而同步容器HashTable虽然是线程安全的,但是锁的粒度太大,
2020-12-28 17:02:46
171
原创 JDK源码阅读(十) : 并发容器——CopyOnWriteArrayList
1. 同步容器和并发容器Java1.5以前,提供线程安全的容器是同步容器,比如Vector、Stack 和 Hashtable,这些容器使用synchronized关键字修饰方法,以保证互斥。这种方式的串行度太高,所以效率很低。Java1.5开始,提供了性能更好的并发容器。并发容器也可分成四大类:List、Map、Set和Queue。2. CopyOnWriteArrayListCopyOnWriteArrayList是ArrayList的线程安全版本,从名称上来看,它是写时复制的ArrayList
2020-12-23 17:21:33
164
原创 JDK源码阅读(九) : 不重复的集合——HashSet
1. SetSet一种存储不重复元素的容器。也就是说,Set容器中的元素,不可以出现一个元素equals另一个元素,并且最多也只能有一个元素为null。Set家族较为常见的实现类是HashSet和LinkedHashSet。本文将介绍这两个类的源码实现( JDK8)。2. HashSetHashSet无法保证集合的迭代顺序,并且也无法保证集合中元素的排列顺序随着时间的推移保持不变。HashSet的实现方式并非线程安全的,在多线程环境下,我们需要在外部进行同步操作。2.1 继承关系和成员属性Ha
2020-12-22 17:06:41
194
原创 JDK源码阅读(八) : 增强的HashMap——LinkedHashMap
1. 继承关系LinkedHashMap继承了HashMap类和Map接口。与HashMap的不同之处在于,LinkedHashMap内部保存了一个双链表,这个双链表将所有的key-value对按照加入顺序或者访问顺序连接起来。并且,这个双链表的顺序,也决定了对该LinkedHashMap的迭代顺序。public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>Lin
2020-12-21 21:57:21
143
原创 JDK源码阅读(七) : 容器——解锁HashMap
1. MapMap是一种存储键值对(key-value)的容器,容器中一个key映射到一个唯一的value,所有的key不可以重复。Map提供了三种集合视图,分别是key的集合(不重复)、value的集合(可重复)和key-value对的集合(entry,不重复)。一个Map实例自身不可以作为自己的key,但是可以作为自己的value。Map家族使用较多的实现类是HashMap、LinkedHashMap。本文将主要介绍这两个类的实现方式 (jdk11版本)。2. HashMapHashMap是
2020-12-20 15:37:26
278
3
原创 JDK源码阅读(六) : 容器——ArrayList
1. List家族List是集合类容器的上层接口,List家族有很多我们常用的容器,这些容器都实现了List接口,使用最频繁的是ArrayList和LinkedList,本文主要讨论这两种容器。2. ArrayListArrayList不是多线程版本的容器,因此不需要做同步操作synchronized,保证了操作的效率,如果我们需要对单个ArrayList容器做多线程操作,可以在外部进行同步控制。而List家族另一个古老的容器类Vector则是多线程版本的,现在使用不多。2.1 继承关系和成员属性
2020-12-13 21:39:03
176
原创 JVM源码阅读(五) : 反射——Field、Constructor和Method
一、Field类Field类提供了某个类或接口中的属性信息,既可以是类属性,也可以是实例属性。1. 字段介绍//这个属性是属于哪个类,通过调用getDeclaringClass方法可以获取到声明这个属性的类private Class<?> clazz;private int slot;//主要用于虚拟机实施反射private String name;//该属性是什么类型的private Class&
2020-12-03 16:07:00
151
原创 JDK源码阅读(四) : 反射——Class
1. 什么是Classjava中一切皆对象。java中所有的类或接口(enum或annotation也是一种接口)其实都是对象,它们都是Class类的实例。数组类型也是一种Class对象,它抽象了所有元素类型相同且维度相同的数组。基础数据类型(比如int、byte)也可以作为Class类的对象。2. 获取Class对象Class对象是在类加载时由java虚拟机自动创建的。/* * 构造器为private,只有JVM才能创建Class对象 */private Class(ClassLoader
2020-11-30 20:38:11
203
原创 JDK源码阅读 (三) : StringBuffer、StringBuilder
2. StringBufferStringBuffer与String类似,不同之处在于,StringBuffer不具有不可变性,它所持有的字符序列可以被修改。也就是说,当我们对本对象调用拼接或剪裁等操作时,最后返回的是这个对象自身。此外,StringBuffer还是线程安全的。类中提供的方法都以Synchronized修饰,表明多线程操作时受到同步机制的限制。虽然保证了线程安全,但是也带来了额外的性能开销。因此,在没有线程安全需求的情况下,推荐使用后文介绍的StringBuilder,它是StringB
2020-11-23 16:04:51
169
原创 JDK源码阅读 (二) : String
1. String1.1 构造String对象有两种方式可以构造一个String对象:String s1 = "dog";String s2 = new String("dog");①第一种构造方式是直接从字符串常量池中取得一个字符串对象"dog",然后s1指向常量池中对应的位置。需要注意的是,当试图从字符串常量池中取得"dog"对象时,发现没有,则会在池中创建该对象。当下一次又要从池中获取"dog"对象时,可以直接取得。这是一种缓存思想。②第二种构造方式是在堆中申请一块区域,使用构造器创建
2020-11-18 11:06:12
3455
原创 JDK源码阅读 (一) : 包装类 Integer
1. Integer1.1 自动装箱和自动拆箱示例代码 ( jdk 8.0 )://通过构造器创建一个Integer对象Integer value1 = new Integer(3);//通过自动装箱获取一个Integer对象Integer value2 = 3;①使用构造器创建Integer对象//调用Integer类的构造方法时,首先依次调用父类的构造方法//然后调用本类的构造方法,给成员value赋值public Integer(int value) { this.val
2020-11-14 18:24:23
121
原创 基于分段、分页技术的虚拟内存技术
1、分段段的定义:把程序与其相关的数据划分到几个段中,比如数据段、代码段和堆栈段。段的长度有限制,但是不要求所有程序的所有段长度都一样。分段:一个程序的所有段被装入内存中时,同样不要求这些段是连续的。分段类似于动态分区,当进入放入内存中时,系统会为这些段分配容量相等的内存空间,随着大小不同的段被放入内存,渐渐会产生外部碎片,即段外部的内存小碎片。如图,放了进程1、2、3之后内存剩余4MB,很难再放入其他段了,这就是外部碎片。段表:段表中的每一项表示一个段,由于段的长度可以不同,所以段表项不仅要给出这
2020-11-09 15:41:00
673
1
原创 并发:发生死锁和处理死锁
1、什么是死锁?死锁指一组互相竞争系统资源的进程/线程永久性地阻塞 (以下线程同进程)。当一组线程中的每个线程都在请求某些资源,而这组线程中被阻塞的其他线程已经占用了这些资源,没有其他任何线程会再释放出这些资源,于是就发生了死锁,死锁是永久的。2、发生死锁的条件死锁有三个必要条件:①互斥。一次只有一个线程可以使用一个资源。②占有且等待。当一个线程已占用了某些资源,在请求等待其他资源的时候,不释放已占用的资源。③不可抢占。其他线程不能强行抢占线程已经占用的资源。这三个条件是发生死锁的必要条件,而
2020-11-05 16:03:12
467
原创 并发:管程
1、为什么使用管程?信号量可以提供线程间的互斥和合作,但是使用信号量来设计一个正确的程序时很困难的,semWait和semSignal可能会分布在整个程序中,很难看出信号量上的这些操作所产生的整体效果。管程(monitor)是一种程序设计语言结构,它提供与信号量相同的功能,但更容易控制。2、什么是管程?管程是包含共享变量以及对共享变量的操作方法的过程,让它们支持并发。管程的特点:①共享变量只能被管程中的方法访问,外部过程不能访问;②一个线程只能通过调用管程的一个方法进行管程;③任何时候,只能
2020-11-04 14:10:20
388
原创 并发:信号量
信号量(Semaphore)是用于进程/线程间传递信号的一个整数值。在信号量上只可以进行三种操作,即初始化、递减和递增,这三种操作都是原子操作。递减semWait():信号量的值减1,如果小于0,则当前线程被阻塞,否则可以继续执行,递减操作也称为P操作;递增semSignal():信号量的值加1,如果小于等于0,则从等待队列中唤醒一个线程,使其就绪,递增操作也称为V操作。信号量模型也被称为PV原语。java语言中,信号量模型由java.util.concurrent.Semaphore实现。semWa
2020-11-04 14:05:33
449
原创 并发:互斥锁
为了保证某段程序的原子性,需要使得同一时刻只有一个线程执行,这就是互斥。一段需要互斥执行的代码称之为临界区。线程进入临界区之前,首先试图执行加锁操作lock(),如果成功加锁,则进行临界区执行,此时该线程持有锁;否则就等待其他进程释放锁;持有锁的线程离开临界区后需要释放锁unlock()。我们都知道锁是用来保护临界资源的,也就是共享变量、共享文件等资源,那么锁的是什么呢?1、synchronized关键字Java中的synchronized关键字是java在语言层面提供的互斥原语,是锁的一种实现。
2020-11-04 14:03:51
333
原创 并发问题的源头
1、并发程序的问题并发是很多问题的基础,也就是操作系统设计的基础。并发包括很多设计问题,其中有进程/线程通信、资源共享与竞争(如内存、IO设备)、进程/线程同步等。由于进程/线程的相对执行速度不可预测,它取决于其他进程/线程的活动、操作系统处理中断的方式以及操作系统的调度策略,于是就产生了以下困难:全局资源的共享存在危险。如果两个进程/线程对同一个全局变量进行读写操作,那么读写的执行顺序非常重要。操作系统的资源分配问题。两个进程互相占用了对方进程所请求的资源,那么就会产生永久地等待,即死锁。定位程
2020-11-03 11:33:35
236
原创 了解进程和线程的基本原理
1、进程的生命周期1)五状态模型:包括新建态、就绪态、运行态、阻塞态和退出态。空—>新建态:创建一个新的进程。新建态—>就绪态:进程被放入内存中后,就可以变成就绪态。就绪态—>运行态:进程被cpu调度执行。运行态—>就绪态:分给该进程的时间片被耗尽,或者更高优先级的进程抢占了cpu。运行态—>退出态:进程已运行完成或取消。运行态—>阻塞态:进程等待某些事件的发送,如I/O操作。阻塞态—>就绪态:进程等待的事件已发生。就绪态—>退出态:子进
2020-10-31 16:57:41
698
原创 单例模式的三种实现方式
1、饿汉式所谓“饿汉式”,是指单实例在类加载时刻就已构造完成。public class Singleton1 { //构造器私有化 private Singleton1(){ } //类加载的初始化阶段,会给静态变量instance赋值,此时单实例得到构建 private static Singleton1 instance = new Singleton1(); public static Singleton1 getInstance(){
2020-10-28 22:20:37
1020
2
原创 如何编写缓存友好的代码
1、存储器层次结构中的缓存存储器的层次结构自顶向下通常是:寄存器——高速缓存L1——高速缓存L2——高速缓存L3——主存——本地磁盘——远程二级存储 (比如分布式文件系统、web服务器)。自顶向下,存储设备的速度越来越慢,价格越来越便宜。上述层次结构中,每一层都缓存来自较低一层的数据对象。本地磁盘可以作为通过网络从远程磁盘取出的文件 (比如web页面) 的缓存,主存可以作为本地磁盘数据的缓存,高速缓存可以作为主存的缓存,依次类推。每一层的缓存包含了下一层数据子集的副本。缓存命中:当程序需要第k+1层
2020-10-18 23:21:15
585
原创 如何协助编译器生成高效代码
优化程序性能的基本策略,一个是选择合适的数据结构和算法,另一个是遵循基本编码规则,避免限制优化的因素,协助编译器产生高效的代码。本文从C语言的层面介绍协助编译器代码优化的一些建议,其他语言比如C++、Java亦可借鉴。示例代码:typedef struct{ long len; data_t* data;}vec_rec, *vec_ptr;//实际程序中,数据类型data_t可以被声明为int、long、double等基本数据类型//从数组中获取一个元素,保存在dest所指位置i
2020-10-17 18:01:55
176
原创 两个正数相加可能会变负?0.1+0.2不等于0.3??
本文并没有系统性的介绍计算机中信息的表示方式,只介绍了一些用户编程时需要注意的的小问题。1、为什么有符号整数负数可表示范围比正数多一个?有符号整数是用补码表示的。具体的表示形式如下,其中,向量的每个元素表示了整数的每一位。负数的最高位是1,非负数最高位是0。假设有符号整数共w位,那么最小的负数最高位为1,其他为0,即 -2^w。最大的正数最高位为0,其他位为1,即 2w-1**。因此有符号整数可表示的范围为**[-2w, 2^w-1]。负数和非负数各占了取值范围的一半,而非负数中有一个数是0,这也就
2020-10-14 17:05:12
2151
原创 HelloWorld从编译到运行,计算机底层到底发生了什么?
本文简要描述了一个简单的HelloWorld程序,从编译到执行,计算机底层做了哪些工作。以 C 程序为例。示例代码:#include <stdio.h>int main(){ printf("hello, world\n"); return 0;}使用文本编辑器编写上述代码,并保存在文本文件中,文件名为hello.c。一、源代码以字节序列的形式存储在文件中源文件 hello.c 是由0和1组成的位序列,8位组成1个字节,每个字节表示程序中的某个文本字符。大部分计算机系统
2020-10-13 12:20:17
1191
原创 数据结构与算法(四)——图的简单应用
一、图的定义树是一种特殊的图,图比树更加复杂,因为图中的每个顶点都可以与其他顶点相连。图又分有向图和无向图。无向图的边没有箭头指向,是双向的;而有向图的边是有箭头指向的,是单向的。边上有权值的图称为带权图。二、图的存储图的存储方式一般有两种:邻接矩阵和邻接表。1、邻接矩阵邻接矩阵使用二维数组存储,假设图中的顶点数为n,则这是一个 n * n 的二维数组。对于非带权图来说,如果顶点 i 到顶点 j 存在指向的边,则二维数组 [i] [j] 位置赋值为1;如果是带权值的图,则二维数组 [i] [
2020-09-29 16:12:38
473
原创 数据结构与算法(三)——堆及其应用
一、什么是堆?作为一种数据结构,堆是一个完全二叉树,且堆中每个节点的值必须大于等于(或小于等于)其子节点的值。对于每个节点的值都大于等于其子节点值的堆,称作“大顶堆”。对于每个节点的值都小于等于其子节点值的堆,称作“小顶堆”。下图为大顶堆。二、堆的存储方式堆作为一种完全二叉树,比较适合用数组来存储,这样就不用像二叉树一样,每个节点都要存储左右子节点的指针了。用数组存储只需要通过数组下标,就可以找到当前节点的左右子节点。如图,数组下标为i的节点,其左子节点的下标为2 * i,右子节点的下
2020-09-28 17:48:16
255
原创 数据结构与算法(二)——动态数据结构
动态数据结构是支持动态的更新操作,里面存储的数据是时刻在变化的。它支持快速地查询、快速插入、删除数据。本文没有对各种动态数据结构的定义和实现进行分析,而是简要地讨论它们的优势、劣势和应用场景。因为对于工程应用来说,更重要的是根据这些数据结构的特点,合理地使用它们。1、散列表散列表使用哈希函数进行映射,因此查找、插入、删除操作的时间复杂度一般为O(1)。限制:①要求设计合适的散列函数。要尽可能让散列后的值随机且均匀分布,这样会尽可能地减少散列冲突,即便冲突之后,分配到每个槽内的数据也比较均匀。并且,散
2020-09-24 15:31:43
1479
原创 数据结构与算法(一)——散列表的应用
一、java容器LinkedHashMapLinkedHashMap是在普通HashMap的基础上新增了Linked功能,那Linked是什么呢?Linked表示当我们将key-value对加入到map容器中时,会按照添加的先后顺序进行排序。也就是说,当执行以下代码时,// 10表示初始大小,0,75表示装载因子,true表示按照访问时间顺序Map<Integer, String> map = new LinkedHashMap<>(10, 0.75f, true);map
2020-09-22 20:19:58
241
原创 从字节码角度看待i++和++i
首先从一个示例代码开始:public class test { public static void main(String[] args) { int i = 0; int j = 0; j = (i++) + (i++) + (i++); i = (++j) + (++j) + (++j); System.out.println("i = "+i+",j = "+j); }}该段代码的执行结果是
2020-09-14 17:25:43
220
原创 深入jvm 07. 垃圾回收(二)垃圾收集器
垃圾收集器种类:新生代GC:Serial、ParNew、Parallel Scavenge老年代GC:Serial Old、Parallel Old、CMS整堆GC:G1组合关系:Serial Old是CMS失败的后备预案;jdk 8 将Serial+CMS、ParNer+Serial Old这两个组合声明为废弃,jdk 9 完全取消了这些组合的支持;jdk 14 弃用Parallel Scavenge+Serial Old组合,并删除了CMS GC。查看默认的垃圾收集器:① -XX:+
2020-08-03 15:09:47
183
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人