JAVA基础的高并发与数据结构

1.列出你了解的实现结合的接口(Collection)的类,并说明他们的作用和区别
    List
        保证元素的储存顺序,而且元素可以重复
    ArrayList
        基于数组,默认初始容量是10,每次扩容一半,内存空间连续,增删改查慢,查询相对比较快,是一个线程不安全的集合
    vector
        基于数组,默认初始容量是10,每次扩容一倍,内存空间连续,增删改查慢,查询相对比较
    快,是一个线程安全-----java中最早的集合
    stack
        栈---先进后出
    LinkedList
        基于链表,内存空间不连续,增删改查较快,查询较慢,线性不安全集合
    set
        元素不可重复,并不保证元素的储存顺序
        Hashset---默认初始化容量是16,加载因子是0.75f,每次扩容一倍,是一个线性不安全集合
    Queue
        队列---先进后出

2.抽象类和接口有什么区别,分别在什么情况下使用?
    构造方法:接口没有构造方法,抽象类有构造方法
    成员变量:接口只有final修饰的常量,final修饰的常量在常量池。抽象类有普通变量和常量
    成员方法:接口只有抽象方法,抽象类有抽象方法和普通方法
    使用区别:接口部分子类必须完成的操作,抽象类所有的子类都必须完成的操作
3.构造函数
    作用:创建对象
        在创建对象的时候实际上是调用对应形式的构造函数。构造函数的函数名要去与类名一致并且没有返回值类型,当一个类没有构造函数的时候,JVM会在编译的时候会自动添加一个无参的构造,构造函数可以重载
4.进程和线程
    进程:程序加载到内存之中之后CPU所计算的过程----进程是计算机资源分配、任务调度的最小单位
        、
         三个维度:
            1.物理内存维度:每一个进程都要分配一块连续的内存空间----首地址,尾地址
            2.执行角度/逻辑角度:每一个进程都能被CPU计算,每一个进程都能挂起然后让另外的进程被CPU计算---对于单核而言,每一个时刻只能计算一个进程。对于Windows而言,默认是只用一个核处理,对于LINUX操作系统,有几个核就用几个核。-----从微观上,计算机是串行处理进程,从宏观上而言,多个进程来并行执行-----多道编程
            3.时间角度:每一个时间段上,进程一定是向前扑进的
        为什么引入进程模型?
            1.减少响应时间,提高使用效率
            2.提高CPU的利用率
        进程的产生事件?
            1.系统启动的时候会创建系统进程
            2.用户请求创建进程---例如打开一个exe文件
            3.父进程自动启动子进程---例如打开QQ的时候附带启动QQ的安全防护进程
        进程的消亡事件?
            1.进程任务执行完成,自然死亡
            2.进程的执行过程中出现错误或者异常,意外身亡
            3.一个进程被另外的进程强制关闭,他杀
    线程:是进程中执行的任务。线程本质上是简化版的进程。一个进程中至少有一个线程。---线程是任务执行的最小的单位
    进程的任务调度算法:1.时间片轮询算法2.优先级调度算法3.短任务优先算法4.FICS

5.NIO
    NIO---NEWIO---NomBlockingIO---非阻塞式IO---基于通道和缓冲区(为所有的原始数据【boolean类型除外】提供缓存支持的数据容器,使用它可以提供非阻塞式的高延伸缩性网络)
    通道
        Channel是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流,而且他们面向缓冲区的。
  正如前面提到的,所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
    通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。

    因为它们是双向的,所以通道可以比流更好地反映底层操作系统的真实情况。特别是在 UNIX 模型中,底层操作系统通道是双向的。


    缓冲区.
        是一个固定数据量的指定基本类型的数据容器。除内容之外,缓冲区还具有位置 和界限,其中位置是要读写的下一个元素的索引,界限是第一个应该读写的元素的索引。基本 Buffer 类定义了这些属性以及清除、反转 和重绕 方法,用以标记 当前位置,以及将当前位置重置 为前一个标记处。

    每个非布尔基本类型都有一个缓冲区类。每个类定义了一系列用于将数据移出或移入缓冲区的 get 和 put 方法,用于压缩、复制 和切片 缓冲区的方法,以及用于分的异类或同类二进制数据序列),访问要么是以 big-endian字节顺序进行,要么是以 little-endian 字节顺序进行。
    特点:
        1.能够进行数据的双向传输---减少了流的数量降低了服务器的内存消耗
        2.由于数据是存储在缓冲区的,所以可以针对缓冲区的数据做定向操作
        3.能够利用一个或者少量的服务器来完成大量的用户的请求的处理---适用于短任务场景

6.BIO
    BIO--BlockingIO--阻塞式IO---阻塞在一些场景会相对影响效率;由于流有方向性,所以在数据传输的时候往往要创建多个流对象;如果一些流长时间不适用却依然会保持连接的话会造成资源的大量浪费;无法从流中准确的抽取一点数据
7.ByteBuffer
    底层是依靠字节数组来储存数据的
    当创建好这个缓冲区的时候,就有了这么几个属性:
        capacity:容量位---表示缓冲区的容量的
        position:操作位---表示要操作的位置---当缓冲区刚刚创建的时候,操作位默认为0
        limit:限制位---表示position所能到达的最大位置---当缓冲区刚刚创建的时候,limit默认为capacity
        put()---向缓冲区添加数据,每添加一个字节的数据,position就会向后挪动一位
        在读取数据之前往往要做一次flip操作----反转缓冲区---先将限制位设置为当前的操作位,然后将操作位归零
        重绕缓冲区---将操作位归零
8.Selector---选择器
    每一个客户端或者服务器端都需要注册到选择器身上,让这个选择器进行管理。选择器在管理的时候需要监听事件
    对应的通道必须注册到对应的选择器的身上,并且得申请对应的事件的权限,后面的选择器才会管理选择对应的事件

9.数据黏包问题的处理
    1.定长---如果数据长度不够,填充无用数据
    2.约定结尾符号---结尾符号可能会和实际数据的内容冲突
    3.约定了起始和结束的协议
10.ConcurrentHashMap---分段锁
        ConcurrentHashMap---异步线程安全的映射---引入了分段锁(分桶锁)
        ConcurrentHashMap 的主干是Segment数组
        Segment继承了ReentrantLock,所以他就是一种可重入锁(ReentarntLock)。在ConcurrentHashMap,一个segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,对不同的Segment的数据进行操作是不用考虑锁竞争的。(就按默认的ConcurrentLeve为16来讲,理论上就允许16个线程并发执行)
11.ConcurrentNavigableMap
    ConcurrentNavigableMap---并发导航映射---允许从指定的位置开始截取一个子映射
    基于跳跃表---  B-tree
        跳跃表是一种随机化的数据结构,目前开源软件Redis和LevelDB都有用到它,它的效率和红黑树以及AVL树不相上下,但跳跃表的原理相当简单,只要能熟练操作链表就能轻松实现一个SkipList
        性质:
            1.由很多层结构组成
            2.最底层的链表包含所有的元素
            3.每一层都是一个有序的链表
            4.如果一个元素出现在这一层的链表中,则它在这一层之下的链表也会出现
            5.每个节点包括两个指针,一个指向同一链表中的下一个元素,一个指向下一层的元素

12.BlockingQueue
    ArrayBlocking---底层是基于数组。遵循队列的先进先出(FIFO)的原则---有界
    LinkedBlockingQueue---底层是基于节点的---有界
    PriorityBlockingQueue---无界;不允许元素为null;队列中的元素对应的类必须实现接口---Comparable,为了重写接口中的compareTo方法来进行排序----自然排序---一般来说自然排序就是升序排序---如果迭代遍历,不保证排序;但是如果是逐个取出的话,保证元素的排序顺序

13.CountDownLatch
    闭锁/线程递减锁----在构造的时候需要传入计数。在计数的线程之后可以进行await操作,直到计数的线程全部执行完成(进行countDown操作来减少一次计数)才会执行await之后的代码---适用于不通的线程进行计数
14.CyclicBarrier
    栅栏。也需要在构造的时候进行计数,在需要阻塞的地方进行await操作,每await一次,计数就减少一次,直到减为0的位置,计数结束,放开阻塞。-----适用于同一线程类产生的多个线程进行计数阻塞

15.Exchanger
    交换机----适用于交换连个线程的信息
16.Semaphore
    信号量---用于限制某段代码在某个时间段内最多只有n个线程进入访问。每个线程进入的时候需要进行acquire操作,会使信号量减少1,直到到了0为止,如果还有新的线程继续过来访问,则会在acquire处阻塞住,直到有线程归还了信号量(release)才能访问
17.ExecutorService
    先交给核心线程处理,如果核心线程已经用完,在来的请求放入工作队列中,如果工作队列已经放满,则创建临时线程来处理请求
    corePoolSize:线程池的大小----线程池中的核心线程的数量---核心线程一旦创建不在销毁
    maximumPoolSize:允许存在的最大线程数量、
    keepAliveTime:存活时间
    unit:时间单位
    workQueue:工作队列----阻塞式队列----在核心线程都被使用的情况下,再来的请求就会放到工作队列中存储
    ExecutorService es = new ThreadPoolExecutor(5, 10, 3000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(5));
    缓存线程池
    没有核心线程池;临时线程池存活时间比较短
    小队列大池子的线程池
    能够相对比较好的应对高并发场景
    不适合于长任务场景
    ExecutorService es = Executors.newCachedThreadPool();
    没有临时线程,全部都是核心线程
    大队列小池子的线程池
    降低服务器的并发压力----适用于长任务场景
    ExecutorService es = Executors.newFixedThreadPool();

18.sleep和wait有什么区别?
    sleep方法需要指定睡眠时间,到点自然醒。释放执行权,不释放锁,被设计在Thread类上,是一个静态方法
    wait方法可以指定等待时间也可以不指定,如果不指定时间需要唤醒,释放执行权,释放锁。被设计在了Object类上,是一个普通的方法,wait方法必须结合锁来使用

19.守护线程
    守护其他线程的执行。当守护的线程结束之后,守护线程无论完成与否都会随之结束,只要代码中出现了守护进程,要么这个进程是守护进程,要么就是被守护线程-----如果出现了多个被守护的线程,那么最后的一个被守护的线程作为结束的标志
20.Lock
    锁-----和synchronized机制类似,但是比synchronized更加灵活
    所有公平策略和非公平策略
        非公平锁和公平锁在获取锁的方法上,流程是一样的;他们的区别主要表现在“尝试获取锁的机制不同”,简单点说“公平锁”在每次尝试获取锁的时候,都是采用公平策略(根据等待队列依次排序等待);而“非公平锁”在每次尝试获取锁的时候,都是采用的非公平策略(无视等待队列,直接尝试获取锁,如果锁是空闲的,即可获取状态,则获取锁)
    synchronized是非公平策略的
    ReentrantLock---默认是非公平策略,可以设置为公平策略。
    通过lock()上锁,通过unlock()方法解锁。
    synchronized和lock的区别
        1.ReentrantLock拥有synchronized相同的并发性和内存语句,此外还多了锁投票,定时锁等候和中断锁等待
        2.synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是lock不行,lock是通过代码实现的,要保证锁定一定被释放,就必须把unlock()放到finally()中
        3.在资源竞争不是很激烈的情况下,synchronized的性能要优于ReentrantLock(编译程序通常会尽可能的进行优化synchronize,另外可读性好),但是在竞争激烈的情况下,synchronized的性能会下降几十倍,但是ReentrantLock的性能能维持常态
    ReentrantLock获取锁定与三种方式:
        1.lock(),如果获取了锁立即返回,如果别的线程持有锁,当前线程一直处于休眠状态,直到获取锁
        2.tryLock(),如果获取了锁立即返回true,如果别的线程持有锁,立即返回false
        3.tryLock(long timeout, TimeUnit  unit),如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false
        4.lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断

21.tcp三次握手
    第一次握手:建立连接时,客户端发送syn(syn=j)包到服务器,并且进入SYN-SENT状态,等待服务器确认,SYN同步序列编号
    第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN(syn=k)包,即SYN+ACK包,此时服务器进入SYN-RECV状态
    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接状态),完成三次握手
    完成三次握手之后,客户端与服务器开始传送数据

22.网络七层模型
    OSI:应用层    文件传输,电子邮件,文件服务,虚拟终端 
         表示层  数据格式化,代码转换,数据加密
         会话层  解除或建立与其他节点的联系
         传输层    提供端对端的接口
         网络层  为数据包选择路由
         数据链路层  传输有地址的帧,错误检测功能
         物理层  以二进制数据形式在物理媒体上传输数据
    TCP/IP层
         应用层    
         传输层    四层交换机,也有工作在四层的路由器
         网络层    路由器,三层交换
         数据链路层    网桥,网卡(一半工作在物理层,一半工作在数据链路层)
         物理层    中继器,集线器,双绞线
23.get和post请求
    1.get请求指示单纯的想服务器获取信息,不会改变数据,post请求可以修改数据,也就是说get只能用于获取信息,post可以获取信息也可以修改、增加数据
    2.get适用于小数据的交互,如果数据量较大就必须用post请求
    3.安全性:get请求安全性低,post请求安全性高,post会把请求参数隐藏,get请求会暴露参数建议:1.get方式请求的安全性较Post方式要差些,包含机密信息的话,建议用Post数据的提交方式2.在做数据查询的时候,建议用Get方式;而在做数据添加、修改、下载或删除的时候,建议用Post方式,

24.JDBC的流程
    1.注册数据库
    2.获取数据库连接
    3.获取传输器
    4.利用传输器,发送sql到数据库执行,返回执行结果
    5.处理结果
    6.释放资源

25.悲观锁和乐观锁
    悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为被人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿数据就会block直到它拿到锁。传统的关系型数据库里面就用到了这种锁机制,比如说行锁,表锁等,读锁,写锁等,都是在做这些锁操作之前先上锁
    乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都是认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间比别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型。这样可以提高吞吐量,想数据库如果提供类似于write_condition机制的其实都是提供的乐观锁
    两种锁各有优点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况,即冲突真的很少发生的时候,这样可以省去锁的开销,加大了系统的整个吞吐量。但如果经常发生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适

26.ReadWriteLock和readLock和writeLock(读写锁和读锁和写锁)
    ReadWriteLock也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个资源可以被多个线程同时读,或者被一个线程写,但是不能同时存在读和写的线程
    使用场合
        假设在程序中定义一个共享数据结构用作缓存,它大部分时间提供读服务(例如:查询和搜索),而写操作占有的时间很少,但是写操作完成之后的更新需要对后续的读服务可见。
27.内存溢出
    内存溢出out of memory,是指程序在申请内存的时候,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出
    内存泄露memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏危害可以忽略不计,但是内存泄露堆积后果很严重,无论内存多少迟早会被占光
    memory leak最终会导致out of memory
28.栈内存溢出(StackOverflowError)
    程序所需要的栈深度大导致,可以写一个死递归程序触发
    如果线程请求的栈容量超过栈允许的最大容量的话,Java 虚拟机将抛出一个StackOverflow异常;如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异
29.堆内存溢出(OutOfMemoryError:java heap space)
    需要分清内存溢出还是内存泄露
    1.如果是内存溢出,则通过调大-Xms,-Xmx参数
    2.如果是内存泄露,则看对象如何被GC Root引用
    对象大于新生代剩余内存的时候,将直接放入老年代,当老年代剩余内存还是无法放下的时候,触发垃圾收集,收集后还是不能放下就会抛出内存溢出异常了
30.持续带内存溢出(OutOfMemoryError:PermGen space)
    持续带中包含方法区,方法区包含常量池
    因此持久带溢出可能是1.运行时常量池溢出2.方法区中保存了Class对象没有被及时回收掉或者Class信息占用的内存超过了我们的配置
    用String.intern()触发常量池溢出
    Class对象未被释放,Class对象占用信息过多,有过多的Class对象。可以导致持久带内存溢出
    •使用一些应用服务器的热部署的时候,我们就会遇到热部署几次以后发现内存溢出了,这种情况就是因为每次热部署的后,原来的Class没有被卸载掉。
    •如果应用程序本身比较大,涉及的类库比较多,但是我们分配给持久带的内存(通过-XX:PermSize和-XX:MaxPermSize来设置)比较小的时候也可能出现此种问题。
    •一些第三方框架,比如spring、hibernate都是通过字节码生成技术(比如CGLib)来实现一些增强的功能,这种情况可能需要更大的方法区来存储动态生成的Class文件。
    •我们知道Java中字符串常量是放在常量池中的,String.intern()这个方法运行的时候,会检查常量池中是否存和本字符串相等的对象,如果存在直接返回常量池中对象的引用,不存在的话,先把此字符串加入常量池,然后再返回字符串的引用。


31.无法创建本地线程
    系统内存的容量不变,堆内存、非堆内存设置过大,会导致能给线程分配的内存不足
     •程序创建的线程数超过了操作系统的限制。对于Linux系统,我们可以通过 ulimit -u 来查看此限制。
    •给虚拟机分配的内存过大,导致创建线程的时候需要的native内存太少。
32.HashSet是如何保证元素不重复的
     1,如果hash码值不相同,说明是一个新元素,存; 
     如果没有元素和传入对象(也就是add的元素)的hash值相等,那么就认为这个元素在table中不存在,将其添加进table;
     2(1),如果hash码值相同,且equles判断相等,说明元素已经存在,不存;
     2(2),如果hash码值相同,且equles判断不相等,说明元素不存在,存;
     如果有元素和传入对象的hash值相等,那么,继续进行equles()判断,如果仍然相等,那么就认为传入元素已经存在,不再添加,结束,否则仍然添加;
33.为什么HashSet是线程不安全的?
     HashMap底层是一个Entry数组,当发生hash冲突时,hashMap是采用链表的方式来解决的,在对应的数组的位置存放链表的头结点。对于链表而言,新加入的节点会从头节点加入
     1.此实现不是同步的,如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保证外部同步(结构上的修改是指添加或删除一个或者多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改)当两个线程同时写入同一个数组的时候,两个想成会同时得到现在的头节点,一个写入之后,第二个写入操作的写入就会覆盖第一个写入操作,早成第一个写入操作的丢失
     2.删除键值对:当多个线程同时操作一个数组位置的时候,也都会取得现在的状态下该位置存储的头结点,然后各自去进行计算操作,之后再把结构写到该数组位置去,其实写回的时候可能其他线程已经修改过这个位置,就会覆盖其他线程的修改
     3.addEntry中当加入一个新的键值对后键值对总数量超过门限值的时候会调用一个resize操作
     当多个线程同时检测到总数量超过门限值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组
34.java线程栈
     线程的每个方法被执行的时候,都会同时创建一个帧(Frame)用于储存本地变量表、操作栈、动态链接、方法出入口等信息、每一个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。如果线程请求的栈深度大于虚拟机锁允许的深度,将抛出StackOverflowError异常;如果VM栈可以动态扩展,当扩展时无法申请到足够内存则抛出OutOfMemoryError异常。
35.JMM(java内存模型)
     JMM定义了java虚拟机在计算机内存中的工作方式
     java的并发采用的是共享内存
     这里的共享内存就是JMM,JMM决定了一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来说,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量储存在主内存之中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本
     线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
     JMM内存模型的基础原理
        指令重排序
             在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。但是JMM通过插入特定类型的Memory Barrier来禁止特定类型的编译器重排序和处理器重排序,为上一层提供一致的内存可见性保障
             1.编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
             2.指令级并行的重排序:如果不存在/在数据依赖性,处理器可以改变语句对应机器指令的执行顺序
             3.内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是乱序执行
        数据的依赖性
             如果两个操作同时访问一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性。编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序
        as-if-serial
             不管怎么重排序,单线程下的执行结果不能被改变,编译器、runtime和处理器都必须遵循as-if-serial语义 
     内存屏障(Memory Barrier )
         通过内存屏障可以禁止特定类型处理器的重排序,从而让程序按预想的流程去执行。内存屏障,又称内存栅栏,是一个CPU指令:
         1.保证特定操作的执行顺序
         2.影响某些数据(或则是某条指令的执行结果)内存的可见性
         如果一个变量是volatile修饰的,JMM会在写入这个字段之后插进一个Write-Barrier指令,并在读这个字段之前插入一个Read-Barrier指令。这意味着,如果写入一个volatile变量,就可以保证:
         1.一个线程写入变量a后,任何线程访问该变量都会拿到最新值。
         2.在写入变量a之前的写入操作,其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入。
     happens-before
         从jdk5开始,java使用新的JSR-133内存模型,基于happens-before的概念来阐述操作之间的内存可见性。
         在JMM中,如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中。
         与程序员密切相关的happens-before规则如下:
             1.程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作。
             2.监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作。
             3.volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。 
             4.传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C。
         注意:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。


36.java反射
     Java反射机制是在运行状态下,对于任何一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称之为java语言的反射机制
     要剖析一个类,必须先要获取该类的字节码文件对象。而剖析使用的就是Class类中的方法。所以先要获取到每一个字节码对应的Class类型的对象
37.HashMap和TreeMap的区别
     HashMap:数组方式存储key/value,线程非安全,允许null作为key和value,key不可以重复,value允许重复,不保证元素迭代顺序是按照插入时的顺序,key的hash值是先计算key的hashcode值,然后在进行计算,每次扩容都会重新计算所有的key的hash值,会消耗资源,要求key必须重写equals和hashcode方法
     默认初始容量是16,加载因子是0.75,扩容为旧容量乘2,查找元素快,如果key一样则比较value,如果value不一样,则按照链表结构存储value,就是一个key后面有多个value
     TreeMap:基于红黑二叉树的NavigableMap的实现,线程安全,不允许null,key不可以重复,value允许重复,存入TreeMap的元素应当实现comparable接口或者实现Comparator接口,会按照排序后的顺序迭代元素,两个相对比较的key不得抛出classCastException。主要用于存入元素的时候对元素进行自动排序,迭代输出的时候就按照顺序输出
38.java多线程的实现方式,及之间的区别,使用场景
     

             


     


 

猜你喜欢

转载自blog.csdn.net/ClearloveXXX/article/details/82145200