目录

resume-1

数据不一致是线程工作内存和主存不一致,这个可以通过volatile解决,cpu缓存和主存之间可以通过mesi缓存一致性协议或者总线锁lock来保证

索引是一种排好序的数据结构

innodb引擎,查看一个磁盘页的大小,show global status like '%innodb_page_size%'; 16KB

主键使用bigint 8byte,分叉地址6byte,可以存放16kb/(8+6)byte=1170个索引元素,叶子节点比较特殊还存放data,假设叶子节点中一个元素不超过1kb,高度为3 可以存放 1170*1170*16 = 2千万

myisam frm/myd/myi,表结构/数据/索引

innodb frm/ibd,表结构/数据+索引

聚集索引:叶子节点包含了完整的数据结构

非聚集索引:叶子节点包含索引以及索引指向的磁盘空间地址

b树和b+树区别:1.冗余索引,2.叶子节点双向指针

为什么需要自增主键,防止过多的分裂/平衡

自增ID可以保证每次插入时B+索引是从右边扩展的,可以避免B+树和频繁合并和分裂(对比使用UUID)。如果使用字符串主键和随机主键,会使得数据随机插入,效率比较差。

1.为什么有了mesi,jmm还要一个volatile来保证可见性?

MESI协议为了提高性能,引入了Store Buffe和Invalidate Queues,还是有可能会引起缓存不一致,还需要再引入内存屏障来确保一致性(比如java中的volatile)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Demo {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName());
            }, String.valueOf(i)).start();
        }

        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
    }
}

面试题

1、大厂面试题 蚂蚁花呗一面(一个小时):

Java容器有哪些?哪些是同步容器,哪些是并发容器?

ArayList和LinkedList的插入和访问的时间复杂度?

java反射原理,注解原理?

新生代分为几个区?使用什么算法进行垃圾回收?为什么使用这个算法?

HashMap在什么情况下会扩容,或者有哪些操作会导致扩容?

HashMap push方法的执行过程?

HashMap检测到hash冲突后,将元素插入在链表的末尾还是开头?

1.8还采用了红黑树,讲讲红黑树的特性,为什么人家一定要用红黑树而不是AVL、B树之类的?

https和http区别,有没有用过其他安全传输手段?

线程池的工作原理,几个重要参数,然后给了具体几个参数分析线程池会怎么做,最后问阻

塞队列的作用是什么?

linux怎么查看系统负载情况?

请详细描述springmvc处理请求全流程?

spring一个bean装配的过程?

讲一讲AtomicInteger,为什么要用CAS而不是synchronized?

美团一面经验

最近做的比较熟悉的项目是哪个,画一下项目技术架构图 JVM老年代和新生代的比例?·YGC和FGC发生的具体场景? jstack.jmap.jul分别的意义?如何线上排查JVM的相关问题? 线程池的构造类的方法的5个参数的具体意义? 单机上一个线程池正在处理服务如果忽然断电怎么办(正在处理和阻塞队列里的请求怎么处理)? 使用无界阻塞队列会出现什么问题? 接口如何处理重复请求? 百度面试题

hashmap hastable 底层实现什么区别?hashtable和concurrenthashtable呢? hashmap和treemap什么区别?底层数据结构是什么? 线程池用过吗?都有什么参数?底层如何实现的? synchronized和Lock什么区别?synchronized什么情况情况是对象锁?什么时候是全局锁为什么? ThrealdLocal 是什么底层如何实现?写一个例子呗? volitile的工作原理? cas知道吗如何实现的? 请用至少四种写法写一个单例模式? 请介绍一下JVW内存模型??用过什么垃圾回收器都说说呗 线上发送频繁Full GC如何处理?CPU使用率过高怎么办? 如何定位问题?如何解决说一下解决思路和处理方法 知道字节码吗?字节码都有哪些?Integer x=5,int y =5,比较x=y都经过哪些步骤? 讲讲类加载机制呗,都有哪些类加载器,这些类加载器都加载哪些文件? 手写一下类加载Demo,知道osgi吗?他是如何实现的??? 请问你做过哪些TVW优化?使用什么方法达到什么效果??? classforlame(“java.lang.String”)和String classgetClassloader() LoadClass(“java.lang.String”)什么区别啊? 今日头条

HashMap如果一直put元素会怎么样?hashcode全都相同如何? AppicationContex的初始化过程? GC用什么收集器?收集的过程如何?哪些部分可以作为GC Roots? Volatile 关键字,指令重排序有什么意义?s/nchronied怎么用? 并发包里的原子类有哪些,怎么实现?cas在CPU级别用什么指令实现 Redis数据结构有哪些?如何实观sorted set?这种数据结构在极端情况树? MySql索引提什么数据结构?B tree有什么特点?优点是什么? 慢查询怎么优化? 项目:cache,各部分职责,有哪些优化点 京东金融面试

Dubbo超时重试;Dubbo超时时间设置 如何保障请求执行顺序 分布式率务与分布式锁(扣款不要出现负数) 分布式session设置 执行某操作,前50次成功,第51次失败:a)全部回滚b)前50次提交第51次抛异常,a)b)场景分别如何设置Spring(传播特性) Zookeeper利部些作用 JVM内存模型 数据库重直和水平拆分 MyBais如何分页;如何设置缓存;MySQL分页 美团面试题汇总

一轮技术面

一、jvm相关

对象在jvm中是怎么存储的? 对象头信息里面有哪些东西? jvm内部如何划分?常量池在哪里? 写一段小程序使栈溢出,堆溢出? 二、GC

GCRoot如何确定,哪些对象可以作为GC Root? GC如何分代的?每代用什么算法回收? CMS过程是怎样的?内部使用什么算法做垃圾回收? 分代垃圾回收过程?

三、并发相关

java中有哪几种锁? synchronized内部原理? ReentrantLock内部实现? HashMap,Hashtable,ConcurrentHashlap区别?内部实现? 原子类内部如何实现的? ArrayBlockingQueue和LinkedBlockingQueue内部如何实现? 四、数据库相关 innoDB索引数据结构? BTree B+Tree区别?为什么使用B+Tree? 五、算法

写程序判断一棵树是不是完全对称的二叉树? 写程序判断两颗二又树是不是相同? 六、其他

Comparable和Comparator区别? 内存溢出和内存泄露分别指什么? 二轮技术面(这轮面试全程懵逼-好多问题记不清了)

一、项目介绍

二、开源架构

RocketMQ?设计介绍?

三轮技术面

一、项目介绍二、开源框架

dubbo如何提供服务?有机器宿掉怎么检测出来?如何找到服务? zk如何管理服务和配置的? tair与redis 有什么区别? redis是单例的吗? mysql的整体架构是怎样的? innodb索引? innodb 主键索引和非主键索引区别? 了解java的nio吗?

三、基础

Hashlap与concurrentlHashMlap比较? 介绍一下java多线程? 线程间如何通信? 四、项目管理

项目开发流程? 如何推动了解整个项目情况? 蚂蚁金服电话二面

自我介绍、工作经历、技术栈 项目中你学到了什么技术?(把三项目具体描述了很久) 微服务划分的粒度 微服务的高可用怎么保证的? 常用的负载均衡,该怎么用,你能说下吗? 网关能够为后端服务带来哪些好处? Spring Bean 的生命周期 HashSet 是不是线程安全的?为什么不是线程安全的? Java中有哪些线程安全的Map? Concurrenthashmap 是怎么做到线程安全的? HashTable你了解过吗? 如何保证线程安全问题? synchronized、lock volatile 的原子性问题?为什么i++这种不支持原子性?从计算机原理的设计来讲下不能保证原子性的原因 happens before 原理cas 操作 lock 和synchronized的区别? 公平锁和非公平锁Java 读写锁 读写锁设计主要解决什么问题? ———————————————— 版权声明:本文为CSDN博主「OnebyWang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/oneby1314/article/details/107968198

杂项

1.乐观锁和悲观锁区别

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

2.恶汉式与懒汉式区别

所谓“懒汉式”与“饿汉式”的区别,是在与建立单例对象的时间的不同。 “懒汉式”是在你真正用到的时候才去建这个单例对象: 比如:有个单例对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Singleton{ 
    private Singleton(){}
    private static Singleton singleton = null //不建立对象
    public static synchronized Singleton getInstance(){
             if(singleton == null) { //先判断是否为空                
                 singleton = new Singleton (); //懒汉式做法 
             }
             return singleton 
     }
}

“饿汉式”是在不管你用的用不上,一开始就建立这个单例对象:比如:有个单例对象

1
2
3
4
5
public class Singleton{ 
    public Singleton(){}
    private static Singleton singleton = new Singleton() //建立对象
    public static Singleton getInstance(){
        return singleton //直接返回单例对象 }}

它有以下几个要素:

  • 私有的构造方法
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态的公有的方法
  1. 使用接口定义常量还是使用final类定义常量

    1
    2
    3
    4
    5
    6
    7
    
    import static Constants.PLANCK_CONSTANT;
    import static Constants.PI;
    public class Calculations {
        public double getReducedPlanckConstant() {
            return PLANCK_CONSTANT / (2 * PI);
        }
    }
    

3.内存屏障

内存屏障,又称内存栅栏,它是一个cpu指令,通过内存屏障可以禁止特定类型cpu的重排序,让程序按照我们预想的流程执行。

内存屏障的功能主要有两个:1.禁止所有指令和内存屏障指令重排序;2.强制刷出各种cpu cache

在JSR规范中定义了4种内存屏障:

  • LoadLoad屏障:(指令Load1; LoadLoad; Load2),在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

  • LoadStore屏障:(指令Load1; LoadStore; Store2),在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

  • StoreStore屏障:(指令Store1; StoreStore; Store2),在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

  • StoreLoad屏障:(指令Store1; StoreLoad; Load2),在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

    StoreLoad屏障是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。

    对于volatile关键字,按照规范会有下面的操作:

    • 在每个volatile写入(执行写)之前,插入一个StoreStore,写入之后,插入一个StoreLoad
    • 在每个volatile读取(执行读)之前,插入LoadLoad,之后插入LoadStore

    https://gitee.com/lienhui68/picStore/raw/master/null/20200726112842.png

4.jmm

https://blog.csdn.net/suifeng3051/article/details/52611310

在并发变成领域有两个关键问题:线程之间的通信和同步

线程之间通信有两种方式,1.共享内存;2.消息传递,典型的共享内存通信方式就是通过共享对象进行通信,在java中典型的消息传递方式就是wait、notify

线程之间同步是指程序用于控制线程之间操作发生相对顺序的机制,在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。

在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的

Java线程之间的通信采用的是过共享内存模型,这里提到的共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

当对象和变量存储到计算机的各个内存区域时,必然会面临一些问题,其中最主要的两个问题是:

1
2
1. 共享对象对各个线程的可见性
2. 共享对象的竞争现象

共享对象对各个线程的可见性

当多个线程同时操作同一个共享对象时,如果没有合理的使用volatile和synchronization关键字,一个线程对共享对象的更新有可能导致其它线程不可见。

想象一下我们的共享对象存储在主存,一个CPU中的线程读取主存数据到CPU缓存,然后对共享对象做了更改,但CPU缓存中的更改后的对象还没有flush到主存,此时线程对共享对象的更改对其它CPU中的线程是不可见的。最终就是每个线程最终都会拷贝共享对象,而且拷贝的对象位于不同的CPU缓存中。

下图展示了上面描述的过程。左边CPU中运行的线程从主存中拷贝共享对象obj到它的CPU缓存,把对象obj的count变量改为2。但这个变更对运行在右边CPU中的线程不可见,因为这个更改还没有flush到主存中: 要解决共享对象可见性这个问题,我们可以使用java volatile关键字。 Java’s volatile keyword. volatile 关键字可以保证变量会直接从主存读取,而对变量的更新也会直接写到主存。volatile原理是基于CPU内存屏障指令实现的

共享对象的竞争现象

如果多个线程共享一个对象,如果它们同时修改这个共享对象,这就产生了竞争现象。

如下图所示,线程A和线程B共享一个对象obj。假设线程A从主存读取Obj.count变量到自己的CPU缓存,同时,线程B也读取了Obj.count变量到它的CPU缓存,并且这两个线程都对Obj.count做了加1操作。此时,Obj.count加1操作被执行了两次,不过都在不同的CPU缓存中。

如果这两个加1操作是串行执行的,那么Obj.count变量便会在原始值上加2,最终主存中的Obj.count的值会是3。然而下图中两个加1操作是并行的,不管是线程A还是线程B先flush计算结果到主存,最终主存中的Obj.count只会增加1次变成2,尽管一共有两次加1操作。

要解决上面的问题我们可以使用java synchronized代码块。synchronized代码块可以保证同一个时刻只能有一个线程进入代码竞争区,synchronized代码块也能保证代码块中所有变量都将会从主存中读,当线程退出代码块时,对所有变量的更新将会flush到主存,不管这些变量是不是volatile类型的。

5.volatile与synchronized的区别

解释volatile与synchronized的区别之前,我们需要先理解线程安全的两个方便:执行控制和内存可见

执行控制的目的是控制代码的执行顺序以及是否可以并发执行

内存可见控制的是线程执行结果对其他线程是否可见,根据jmm,线程在具体执行时,会从主存中拷贝数据到线程的工作内存,操作完成后再把结果从工作内存刷到主存

synchronized关键字解决的是执行控制的问题,它会阻止其它线程获取当前对象的监控锁,这样就使得当前对象中被synchronized关键字保护的代码块无法被其它线程访问,也就无法并发执行。更重要的是,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作,都happens-before于随后获得这个锁的线程的操作。

volatile关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接刷到主存,即保证了变量的可见性。这样就能满足一些对变量可见性有要求而对读取顺序没有要求的需求。

使用volatile关键字仅能实现对原始变量(如boolen、 short 、int 、long等)操作的原子性,但需要特别注意, volatile不能保证复合操作的原子性,即使只是i++,实际上也是由多个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。

volatile和synchronized的区别

volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的

volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性

volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

6.happens-before

从jdk5开始,java使用新的JSR-133内存模型,基于happens-before的概念来阐述操作之间的内存可见性。

在JMM中,如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中。

与程序员密切相关的happens-before规则如下:

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作。

  • 监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作。

  • volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。

  • 传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C。

    注意:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。

7.java内存泄露的原因

长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。

1.集合类,集合类仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。这一点其实也不明确,这个集合类如果仅仅是局部变量,根本不会造成内存泄露,在方法栈退出后就没有引用了会被jvm正常回收。而如果这个集合类是全局性的变量(比如类中的静态属性,全局性的map等即有静态引用或final一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减,因此提供这样的删除机制或者定期清除策略非常必要。 2.单例模式。不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露

8.有return的情况下try catch finally的执行顺序

结论:

  1. 不管有木有出现异常,finally块中代码都会执行;
  2. 当try和catch中有return时,finally仍然会执行;
  3. finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
  4. finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

final finally finalize

9.垂直分库到水平分库到读写分离

垂直分库:按照业务垂直划分。比如按照业务划分资金、会员、订单三个数据库。需要解决的问题是:跨数据库的事务,join查询等问题。

水平分库:按照一定的规则水平划分,需要解决的问题:数据路由、组装

读写分离:对于时效性不高的数据,可以通过读写分离缓解数据库压力。需要解决的问题:在业务上区分哪些数据是允许一定时间延迟的,以及数据同步问题。

10.跨库join的几种解决思路

1.全局表,例如数据字典

2.字段冗余

3.系统层封装,通常都会通过缓存来避免频繁rpc通信和数据库查询的开销

11.订单表的分库分表设计

订单号or uid,问题:如果是订单号,查询一个人的所有订单;如果是uid,根据订单号查询订单详情

一般使用方案二的比较多,一个用户的所有订单,都在一张表里面,那么做分页展示的时候,就容易。

uid的问题解决:通过orderid查询到uid,再路由到指定库,平时查询的时候,走内存缓存查询。如果查询不到,再走数据库查询一下关系。这样速度就很快了。

当然也可以在订单号里面增加分库分表信息,例如淘宝的买家卖家库

思考:

b2b平台的订单分卖家和买家的时候,选择什么字段来分库分表呢?

上面讨论的情况是,b2c平台。订单的卖家就一个,就是平台自己。

b2b平台,上面支持开店,买家和卖家都要能够登陆看到自己的订单。

先来看看,分表使用买家id分库分表和根据卖家id分库分表,两种办法出现的问题

  • 如果按买家id来分库分表。有卖家的商品,会有n个用户购买,他所有的订单,会分散到多个库多个表中去了,卖家查询自己的所有订单,跨库、跨表扫描,性能低下。
  • 如果按卖家id分库分表。买家会在n个店铺下单。订单就会分散在多个库、多个表中。买家查询自己所有订单,同样要去所有的库、所有的表搜索,性能低下。

所以,无论是按照买家id切分订单表,还是按照卖家id切分订单表。两边都不讨好。


淘宝的做法是拆分买家库和卖家库,也就是两个库:买家库、卖家库。

买家库,按照用户的id来分库分表。卖家库,按照卖家的id来分库分表。

实际上是通过数据冗余解决的:一个订单,在买家库里面有,在卖家库里面也存储了一份。下订单的时候,要写两份数据。先把订单写入买家库里面去,然后通过消息中间件来同步订单数据到卖家库里面去。

买家库的订单a修改了后,要发异步消息,通知到卖家库去,更改状态。

如何快速根据订单号定位所在库所在表呢?

思路:订单号里面,最好是要有分库分表信息。

淘宝的是在订单号里面添加了卖家id末2位、买家id末2位。这样的好处是干嘛呢?直接定位到具体的库、具体的表去了。

12.java中函数形参是传值还是传引用

java中的形参是复制实参的一份拷贝,对于引用类型则是复制引用的一份拷贝(在栈中的拷贝),所以在函数中改变形参是无法改变实参的值的,改变引用只是形参所代表的引用指向另外的新的对象,而实参的引用还指向原来的对象;改变形参引用的成员当然会影响实参引用成员的值,因为他们的引用都指向同一个对象。

13.java不可变类机制与String不可变性

定义

  • 不可变类:类的实例一旦创建后就不能改变其成员变量值。如jdk内部自带的很多不可变类:Integer、Long和String等
  • 可变类:创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。

为什么设计不可变类

不可变对象是线程安全的,不需要利用特殊机制来保证同步问题,因为对象的值无法改变;易于构造、使用和测试

不可变类设计方法

  1. 类添加final修饰符,保证类不被继承

    防止子类覆盖父类方法并且继承类可以改变成员变量值

  2. 保证所有成员变量必须私有,并且加上final修饰

  3. 不提供改变成员变量的方法,包括setter

  4. 通过构造器初始化所有成员,进行深拷贝

    如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。例如:

    http://img.cana.space/picStore/20210414114606.png

  5. 在getter方法中不要直接返回对象本身,而是克隆对象,并返回对象的拷贝

String对象的优缺点

优点:1.字符串常量池的需要,2.线程安全,3.类加载要用到字符串,不可变性提供了安全性,确保类被正确加载

缺点:如果有对String对象值经常改变的需求,会创建出大量的String对象

String对象是否真的不可变

可以通过反射改变

14.代理模式和装饰者模式的异同

相同点:都实现了基础对象实现的接口;在其自身对象中保存了被代理/被装饰对象的引用

不同点:装饰是扩展被装饰对象的功能,代理是控制对被代理对象访问

15.指令重排序

定义:JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的【指令顺序】进行【重新排序】。

目的:为了在不改变程序执行结果的前提下,优化程序的运行效率。(需要注意的是,这里所说的不改变执行结果,指的是【单线程】下的程序执行结果。)

16.单例模式volatile

new一个对象这行代码查看字节码可以看到有5个指令

1
2
3
4
5
0 new #2 <java/lang/Object>  // step1 开辟内存空间
3 dup
4 invokespecial #1 <java/lang/Object.<init>> // step2 初始化
7 astore_1 // step3 将这块内存的地址赋值到栈中的局部变量表中,也就是建立o和内存空间关联
8 return

如果step2和step3发生指令重排就会导致对象还没有被初始化就已经建立关系,此时另一个线程刚好判断对象是否为空时通过进而使用出错。使用volatile可以防止指令重排。

17.同步/异步/阻塞/非阻塞

“阻塞”与"非阻塞"与"同步"与“异步"不能简单的从字面理解,提供一个从分布式系统角度的回答。

同步和异步关注的是消息通信机制,调用者主动/被动等待被调用者返回的结果,主动等待是指在没有得到结果之前该调用不返回,但是一旦调用返回就得到最终结果了。异步是指调用发出后直接返回,所以没有返回结果,被调用者通过状态或者通知来告诉调用者处理结果或者通过回调函数处理这个调用。

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会被唤醒。 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

两两之间没有必然联系,阻塞和非阻塞描述的是一种状态,同步和异步描述的是一种消息通知方式。Epoll是同步非阻塞。

同步IO模型:BIO、NIO、多路复用器 多路复用器只能给你状态,最终还是线程自己读写,所以都是同步模型。

异步IO模型:windows:IOCP 内核有线程负责将数据拷贝到程序的内存空间。

18.IO模型

bio -> nio -> select/poll -> epoll

select/poll 弊端

  1. 重复传递fd, 解决:增量式传递,内核开辟空间(红黑树结构)保留fd
  2. 每次select、poll 都要重新遍历全量的fd 解决:中断,callback,增强

解决方案:中断之前将数据存放在buffer里,中断来了之后将红黑树上的fd标识一下,仅此而已。

epoll能够高效支持百万级别的句柄监听。

epoll高效,是因为内部用了一个红黑树记录添加的socket,用了一个双向链表接收内核触发的事件。是系统级别的支持的:

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。

eventpoll结构体如下所示:

1
2
3
4
5
6
7
8
struct eventpoll{
    ....
    /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
    struct rb_root  rbr;
    /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
    struct list_head rdlist;
    ....
};

每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。

这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。

而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

19.TCP

面向连接的、可靠的 传输协议

面向连接:sap、三次握手开辟资源、四次挥手释放资源、socket

可靠:确认机制保证了可靠的传输

20.长连接与短连接区别

https://blog.csdn.net/luzhensmart/article/details/87186401

**长连接:**连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接(一个TCP连接通道多个读写通信);

**短连接:**连接→数据传输→关闭连接;