43-查漏补缺2

触发Full gc的条件

  • 调用System.gc()
  • 老年代空间不足:统计得到的minor GC晋升到老年代的平均大小大于老年代的剩余空间具体的CMS GC 中的promotion failed(在Minor GC的过程中,幸存者to区容量不足以容纳Eden区和另一个Survivor区存活的对象,那么多余的被移动到老年代叫做过早提升,这会导致老年代中短期存活对象的增长,可能会引发严重的性能问题,再进一步,如果老年代满掉了,会进行一次Full GC,这将导致遍历整个堆,称为提升失败)
  • 永久代空间不足
  • 堆中产生的大对象超过阈值,-XX PretenureSizeThreashold进行设定
  • 老年代连续空间不足:CMS GC出现的concurrent mode failure(老年代碎片化严重)

线程池拒绝策略

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
  • ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

char 和varchar的区别?

  • char的存取速度还是要比varchar要快得多,方便程序的存储与查找;但是char也为此付出的是空间的代价,因为其长度固定,所以会占据多余的空间,可谓是以空间换取时间效率。varchar则刚好相反,以时间换空间
  • 声明式的char(M)长度固定,M在0-255中,varchar长度可变,范围是0-65535

Redis的数据结构跳跃表?

跳表就是链表与二分法的结合,链表从头节点到尾节点都是有序的,可以进行跳跃查找(形如二分法),降低时间复杂度,通过多级索引的方式,越高级的索引包含的范围越大,数据量越少

å¨è¿éæå¥å¾çæè¿°

 Mysql的主从复制

主从复制是指把数据从一个数据库服务器的主节点上复制到其他一个或者多个从节点的数据库服务器,Mysql数据库默认采用的是异步复制的方式,从节点数据库不用一直访问主服务器也可以实现更新数据

主节点Log Dump Thread:从节点连上主节点,向主节点发送请求,请求读取主库二进制文件binlog日志的指定位置(binlog position)之后的日志内容,主节点在接收到请求之后,将这部分内容返回给从节点

从节点的IO线程:Start Slave命令后,将接受的日志内容更新到relay log中,并且将读取到的binlog file name(文件名)和position(位置)保存在master-info文件中,以便在下一次读取的时候能告诉Master节点,我需要从哪个位置以后的内容

从节点SQL线程:当SQL线程检测到relay-log中新增了内容,会解析其内容,并且在本数据库中执行

主从复制有几种类型?

同步复制:master的变化,必须等待slave-1...slave-n完成后才能返回

异步复制:类似ajax请求,master只需要完成自己数据库操作即可,至于salves是否收到二进制日志,是否完成操作,不需要关心

半同步复制:master只保证slaves中的一个操作成功,就返回,其他slave不管,这个功能是google为mysql引入的

有哪些方式能保证主从复制的一致性?

半同步复制:之所以会读到旧的数据是因为主从同步需要一个时间段,而读取请求可能刚好就发生在同步阶段,为了读取到最新的数据,需要等主从同步完成之后,主库上的写请求再返回,其缺点在于写请求延时增加,吞吐量降低

  • 系统对主节点进行一个写的操作
  • 等到主从同步完成之后,写的请求才返回
  • 读从库,因为主从同步已经完成,从而读到最新的数据

利用中间件:借助中间件的路由作用,对服务的读写请求进行分发,从而避免出现不一致的问题,所有的读写请求都走数据中间件,通常情况下,写请求路由到主库,读请求路由到从库,记录所有路由到主库的key,在主从同步时间窗口内,如果有请求访问中间件,此时有可能从库还是旧数据,就把这个key的读请求路由到主库,此后依旧对读请求路由到从库,虽然可以保证数据的绝对一致,但是也带来成本上升的问题

利用缓存:将某个库的某个key要发生写操作,记录在cache里,并且设置超时时间,然后修改数据库,当读请求发生的时候从cache里查看,对应的key有没有相关的数据,如果击中了缓存,说明发生过写操作,于是路由到主库读取最新的数据,如果miss,就路由到从库,继续读写分离操作,多了一步cache操作,带来了缓存与数据库一致性的问题

如何发现一个线上的慢sql语句?

  • 修改数据库配置:slow_query_log设置为on,long_query_time设置慢sql的时间,slow_query_log_file记录日志的文件名,log_query_not_using_indexes,记录没有用到索引的sql,持久化可以在配置文件的my.ini里配置
  • linux下有一个show processlist命令,显示哪些线程正在运行,核心主要看id(线程id)time(消耗时间),state列,info(正在执行的sql语句)

state需要注意的状态:

  • copying to tmp table on disk:现有索引结构无法覆盖查询条件,建立一个临时表来满足查询要求,由于临时结果集大于tmp_table_size,正在将临时表从内存存储转为磁盘存储来节省内存,产生巨大的IO压力
  • Locked:该SQL被其他查询锁住了
  • Sending Data:并非发送数据,而是正在从不同磁盘碎片抽取数据的过程,通常是因为某查询影响结果集过大,也就是索引项不够优化
  • Order By:和SendingData类似,结果集过大,排序条件没有索引化,需要在内存中进行排序
  • explain关键字

Type属性:

system:表只有一行记录

const:索引一次就找到了

eq_ref:对于每个索引键,表中只有一条记录与之匹配

ref:返回匹配某个单独值的所有行

range:检索给定范围的行,一般是where语句中出现了bettween,<,>,in等查询

index:需要遍历索引树

all:需要遍历整个表

Extra:

using FileSort:mysql无法利用索引完成排序操作

using temperary:使用临时表保存了中间结果

using index:使用了覆盖索引,避免了访问表的数据行

mysql里的各种log?

基于此图的问题:(buffer虽然读写快,但是会随着进程的退出而退出,所以关键的undolog没有buffer)

如果数据刚刚保存到buffer pool然后redologbuffer还没有写入会怎么样?无所谓,因为意外的宕机,该事务并没有成功(没有固化到磁盘上),直接回滚就行了,在mysql重启的时候,读取redo log文件,把状态加载到bufferpool中,而通过redo log文件恢复的状态和宕机前事务的状态一致

bin-log:记录了数据表结构和数据变更用于主从复制和基于时间点恢复数据,它会一直揭露,不会擦除以前的记录,单机模式下binlog不是必须的,单单依靠redolog就拥有了crash-safe能力了

redo-log:

  • Mysql引入缓存buffer pool来解决磁盘IO在并发场景下性能差的问题,读数据的时候,先从缓存中读,没有再去磁盘读之后再放入缓存,写数据的时候,先向缓存中写,此时缓存中的数据页数据会变更,该数据页叫做脏页,缓存中的数据后续会按照设定的策略定期刷新到磁盘中,这个过程叫刷脏页,问题在于,如果缓存中的数据还没有刷新到磁盘,mysql宕机怎么办?
  • redolog就是用来解决这个问题的,它记录的是事务发生之后的数据状态(包含两部分内存中的日志缓冲(redologbuffer)和磁盘上的日志文件(redolog file顺序IO)),数据库在修改数据的时候,先把更新记录写到undo log中,再修改buffer pool中的数据,再把成功更新后的数据状态写入到redolog buffer中,提交事务的时候,调用fsync把redo log刷入磁盘,至于缓存中的数据何时刷入磁盘,由后台线程异步处理,此时redolog的事务状态是prepare,还未真正提交成功,要等binlog日志写入磁盘后才会变为commit;redolog是顺序IO所以写入速度很快,文件体积很小,恢复速度也很快,它采用的是大小固定,循环写入的方式,在redolog满了要擦除记录之前,必须保证这些记录已经被刷新到磁盘中了,在擦除期间,不能接收新请求导致mysql性能下降;

redo-log和bin-log的区别:

redo log记录了sql语句以及其他api,对表数据产生的变化,也就是说,redolog记录的是数据的物理变化,binlog记录的是数据的逻辑变化,这也就是为什么redolog可以用来crash safe而binlog不可以的原因

redolog的两阶段提交:更新内存后,redolog状态为prepare为第一提交阶段,binlog写完之后,将状态改为commit,为第二提交阶段,目的其实就是为了保证数据的一致性

mysql的数据恢复策略是:如果redolog是完整的,直接用redolog恢复,如果redolog是prepare状态,此时要去判断binlog是否完整,如果完整就提交redolog,再用redolog恢复,不完整就使用回滚事务,丢弃数据

undo-log:回滚和多版本控制(MVCC),存储的是逻辑日志,保证原子性的关键,它记录事务开始的时候原始的数据版本,当再次对这行数据进行修改的时候,产生的记录会写到redo log中,undolog负责回滚,redolog负责前滚(事务已经执行了commit,但事务内修改脏页只有一部分数据被刷盘,另外一部分还在buffer pool中,此时数据库宕机重启,就要使用前滚将未来得及刷盘的数据从redo log中恢复出来并且刷盘)

relay-log:relay log各方面和binlog差不太多,区别是从服务器IO线程将主服务器的二进制日志读取过来并且记录到服务器本地文件,然后sql线程会读取relay-log的内容并且应用到从服务器,从而保证数据一致性

协程?

是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程,协程不归操作系统内核所管理,在用户态执行,因此被称为用户级别的线程,其开销远远小于线程,线程和进程都是同步机制,协程是异步

值传递和引用传递的区别?

值传递:对于基本变量和包装类而言,传递的是该变量的一个副本,改变副本不影响原变量

引用传递:对于对象型变量而言,传递的是该对象地址的一个副本,而不是原对象本身

理解一下以下代码的不同:

StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
    builder.append("4");
}
foo(sb); // sb 被改变了,变成了"iphone4"。

StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
    builder = new StringBuilder("ipad");
}
foo(sb); // sb 没有被改变,还是 "iphone"。

 常见的异常和错误?

异常:

  • nullpointerexception:空指针异常
  • classnotfoundexception:指定的类不存在
  • arithmeticexception:数学运算异常
  • arrayindexoutofboundsexception:数组越界异常
  • illegalargumentexception:方法参数错误,比方是int值必须大于0,你传个-1进去
  • illegalaccessexception:没有访问权限异常

错误:

  • stackoverflow:栈溢出,应用递归的层次太深导致的堆栈溢出错误
  • unknownerror:用于指示java虚拟机发生了未知的严重错误
  • outofmemoryerror:内存溢出错误
  • nosuchmethoderror:方法不存在错误
  • nosuchfielderror:域不存在错误
  • abstractmethoderror:抽象方法错误,在试图调用抽象方法的时候抛出

CopyOnWriteArrayList

继承了List,RandomAccess,Cloneable,Serializable接口

add方法:(set和remove方法也会加锁)

获取原数组->复制一个新的数组->将新的元素添加到新数组里->将原数组的引用指向新数组,整个过程使用ReentrantLock进行加锁

get方法:

读的时候不会加锁,就是普通的读方法

JMM内存模型?

java内存模型是一种虚拟机的规范,JMM规范了Java虚拟机和计算机内存是如何协同工作的:规定了所有的变量都存储在主内存,主内存是共享区域,所有线程都可以访问,但是线程对变量的操作必须在工作内存中进行,首先要讲主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成之后再将变量写回主内存,不能直接操作主内存中的变量,工作内存是每个线程的私有区域,不同的线程之间无法访问对方的工作内存

它解决的问题是:多线程通过共享信息通信的时候,存在的本地内存数据不一致,编译器会对代码指令重排序带来的问题

AQS原理和组件

AQS的核心思想是:如果请求的资源处于空闲状态,则将当前请求资源的线程设置为有效工作线程,并且将共享资源设定为锁定状态,如果被占用,那么需要一套线程等待以及被唤醒时锁的分配机制,这个机制是使用CLH队列实现的,即将获取不到锁的线程加入到队列中;

  • AQS使用一个volatile修饰的int变量state来表示同步状态,通过内置的FIFO队列完成资源的排队工作,使用CAS实现原子操作对state进行修改,其底层使用了模板方法模式类似于TryAcquire这样的方法,默认情况下都抛出UnsupportedOperationException
  • 如果当前线程竞争锁失败,aqs会把当前线程以及等待状态构成一个node加入到同步队列,同时阻塞该线程,当获取锁的线程释放锁的时候,会从队列中唤醒一个阻塞的节点

基于AQS的同步组件:Semaphore信号量,CountDownLatch(某一个线程运行前需要等待n个线程运行完毕),CyclicBarrier(让一组线程达到一个屏障(同步点)的时候被阻塞,直到指定数量的线程到达,被拦截的线程才会继续干活)

多条件锁ConditionObject内部类:实现了Condition接口,基于Lock实现,其内部维护等待队列(区别于同步队列),调用condition的signal方法并不表示线程马上可以执行,signal方法的作用是将线程所在节点从等待队列移除,然后加入到同步队列中,线程的执行始终由是否占有锁来决定

Node节点中定义了五个属性:

  • 用于构建双向链表的next和prev
  • 对于单项链表可以使用nextWaiter,用于独占模式下条件队列的访问
  • waitStatus:有4个int型的状态常量
  1. SINGNAL(-1,表示后继节点在等待当前节点唤醒,后继节点入队的时候,会将前驱节点状态更新为SIGNAL)
  2. CANCELLED(1,线程已经被取消调度,当timeout或者被中断,会触发变更为此窗台,进入该状态的节点不会再发生变化)
  3. CONDITION(-2,表示节点等待在condition上,当其他线程调用了Condition的signal方法后,CONDITION状态的节点将从等待队列转移到同步队列中,等待获取同步锁)
  4. PROPAGATE(-3,共享模式下,前驱结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点)
  5. 0:新节点入队时的默认状态

负值表示节点处于有效等待状态,正值表示节点已经被取消,所以源码中很多地方使用>0和<0来判断节点状态是否正常

  • Thread线程实体

acquire方法:如果获取到资源,线程返回,否则进入等待队列,且整个过程忽略中断的影响,也正是lock的语义,且不仅限于lock,函数流程如下:

  1. tryAcquire()尝试直接去获取资源,如果成功则直接返回(这里体现了非公平锁,每个线程获取锁时会尝试直接抢占加塞一次,而CLH队列中可能还有别的线程在等待);
  2. addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
  3. acquireQueued()使线程阻塞在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
  4. 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

tryAquire(int)方法是一个钩子方法,利用了模板设计模式,具体资源的获取和释放交由自定义同步器去实现,这个方法尝试去获取独占资源,如果获取成功返回true否则返回false:

protected boolean tryAcquire(int arg) {
2         throw new UnsupportedOperationException();
3     }

addWaiter(Node)方法作用是将当前线程加入到等待队列的队尾,并且返回当前线程所在节点

private Node addWaiter(Node mode) {
    //以给定模式构造结点。mode有两种:EXCLUSIVE(独占)和SHARED(共享)
    Node node = new Node(Thread.currentThread(), mode);
    //尝试快速方式直接放到队尾。
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //上一步失败则通过enq入队。
    enq(node);
    return node;
}
private Node enq(final Node node) {
    //CAS"自旋",直到成功加入队尾
    for (;;) {
        Node t = tail;
        if (t == null) { // 队列为空,创建一个空的标志结点作为head结点,并将tail也指向它。
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {//正常流程,放入队尾
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

acquireQueued(Node,int)方法,进入这个方法说明线程获取资源失败,已经被放入到等待队列尾部了,下一步应该进入等待状态休息,等待其他线程彻底释放资源后唤醒自己,自己再拿到资源,然后就可以去干自己的事情了

  • 节点进入队尾后,检查状态,找到安全休息点
  • 调用park进入waiting状态,等待unpark或者interrupt唤醒自己
  • 被唤醒后,看自己是不是有资格拿到号,如果拿到,head指向当前节点,并返回从入队到拿到号的整个过程是否被中断过,如果没有拿到继续流程1
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;//标记是否成功拿到资源
    try {
        boolean interrupted = false;//标记等待过程中是否被中断过

        //又是一个“自旋”!
        for (;;) {
            final Node p = node.predecessor();//拿到前驱
            //如果前驱是head,即该结点已成老二,那么便有资格去尝试获取资源(可能是老大释放完资源唤醒自己的,当然也可能被interrupt了)。
            if (p == head && tryAcquire(arg)) {
                setHead(node);//拿到资源后,将head指向该结点。所以head所指的标杆结点,就是当前获取到资源的那个结点或null。
                p.next = null; // setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了!
                failed = false; // 成功获取资源
                return interrupted;//返回等待过程中是否被中断过
            }

            //如果自己可以休息了,就通过park()进入waiting状态,直到被unpark()。如果不可中断的情况下被中断了,那么会从park()中醒过来,发现拿不到资源,从而继续进入park()等待。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;//如果等待过程中被中断过,哪怕只有那么一次,就将interrupted标记为true
        }
    } finally {
        if (failed) // 如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。
            cancelAcquire(node);
    }
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;//拿到前驱的状态
    if (ws == Node.SIGNAL)
        //如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了
        return true;
    if (ws > 0) {
        /*
         * 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。
         * 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被保安大叔赶走了(GC回收)!
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
         //如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
private final boolean parkAndCheckInterrupt() {
     LockSupport.park(this);//调用park()使线程进入waiting状态
     return Thread.interrupted();//如果被唤醒,查看自己是不是被中断的。
 }

 release(int)方法,对应unlock的语义

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;//找到头结点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//唤醒等待队列里的下一个线程
        return true;
    }
    return false;
}

release()根据tryRlease()的返回值来判断该线程是否已经完全释放掉资源

protected boolean tryRelease(int arg) {
     throw new UnsupportedOperationException();
 }

以下函数用一句话概括就是用unpark唤醒队列中最前面那个未放弃线程

private void unparkSuccessor(Node node) {
    //这里,node一般为当前线程所在的结点。
    int ws = node.waitStatus;
    if (ws < 0)//置零当前线程所在的结点状态,允许失败。
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;//找到下一个需要唤醒的结点s
    if (s == null || s.waitStatus > 0) {//如果为空或已取消
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev) // 从后向前找。
            if (t.waitStatus <= 0)//从这里可以看出,<=0的结点,都是还有效的结点。
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);//唤醒
}

 运行时数据区可能出现的异常?

本地方法栈,虚拟栈:超出栈的深度可能产生StackOverFlow异常,栈也可以动态扩展,如果无法申请到足够的内存也会抛出OOM

方法区:jdk6的时候还是永久代,抛出的异常是OOM:Permanent Space,后续改成了元空间OOM:Metaspace

堆:OOM:java heap space

JAVA对象创建的五个步骤,给对象分配空间的两种方法?对象头里有啥?

类加载检查,分配内存,初始化零值,设置对象头,执行init方法

  • 指针碰撞:如果java堆中的内存是绝对规整的,已经被使用的内存和空闲内存各占一边,二者之间的分界点通过一个指针来表示,需要分配内存的时候把指针向空闲的地方挪动一段与对象大小相等的距离
  • 空闲列表:如果java堆中内存并不规整,虚拟机会维护一个列表,表上记录可用内存块,分配内存时找到一个足够大的内存空间划分给对象实例

对象头包含两部分数据:运行时元数据(哈希值,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳)和类型指针(指向类的元数据)

JVM的内存分配策略

对象优先在Eden区分配

大对象进入老年代 -XX:PertenureSizeThreshold参数

长期存活对象进入老年代

动态对象年龄判断:如果幸存者空间中相同年龄所有对象大小的总和大于其空间的一半,年龄大于等于该年龄的对象进入老年代,无需等到默认15的年龄

空间分配担保:发送minor gc之前,检查老年代可用空间是否大于新生代所有对象总空间,成立则说明minor gc是安全的,否则查看参数是否运行担保失败,如果运行,继续检查老年代最大可用内存是否大于历次晋升到老年代对象的平均大小,大于则尝试minor gc,否则进行full gc

STW是什么SafePoint是什么?

stop the word:进行垃圾回收的时候停止所有活动线程的操作

safePoint:代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前状态是安全的,如果有需要可以在此位置暂停

类加载过程简要描述一下?

  • 加载:获取编译后的class文件,在方法区生成类信息,静态数据结构,在堆区生成该类的class对象
  • 验证:文件格式,元数据,字节码,符号引用这些的验证
  • 准备:为类变量(静态变量),分配内存并且进行赋0值,如果实例变量被final修饰,也会在准备阶段进行赋0值操作
  • 解析:将常量池中的符号引用转为直接引用
  • 初始化阶段:对类的静态变量和java代码中的static{}块包裹的代码进行执行

数据库三大范式

列不可再分,保证每一列的原子性

属性完全依赖于主键

属性不依赖于其他非主属性

Redis缓存为什么这么快?

  • 完全基于内存
  • 数据结构简单,操作也简单
  • 采用单线程,避免上下文切换的开销
  • 使用非阻塞多路IO复用模型
  • 底层模型不同,自己构建了VM机制

多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗)

非阻塞IO:非阻塞等待,隔一段时间区查询IO事件是否就绪

select等函数是如何获取网络事件的?在获取事件时,把所有连接传给内核,再由内核返回产生了事件的连接,然后再用户态中处理这些连接对应的请求,转向内核态的时候使用的是系统调用方式

select和poll并没有本质区别,缺陷在于客户端越多,socket集合越大,遍历集合的时候会带来很大开销

epoll:内核中使用了红黑树来关注进程中所有的socket,通过对这个红黑树的管理,不需要向select和poll一样每次操作都传入整个socket集合,减少了内核和用户空间大量的数据拷贝和内存分配;它还用了时间驱动机制,内部维护了一个链表来记录就绪事件,只有事件发生的socket传递给应用程序,不需要像..一样扫描无事件的集合,提高了检测效率

 同一个进程下的线程哪些是私有的?

线程共享的环境包括:进程代码段,进程的公有数据,进程打开的文件描述符,信号的处理器,进程的当前目录和进程用户ID,进程组ID

线程也拥有自己的个性:线程ID,寄存器组的值,线程的堆栈,错误返回码变量,线程优先级和线程的信号屏蔽码(每个线程感兴趣的信号不同,但是所有的线程都共享信号处理器)

Top命令能查看哪些状态?

load average:数据每隔五秒查询一次活跃的进程数,按照特定的算法计算出的数值,如果这个值除以逻辑CPU的数量,结果高于5就说明系统在超负荷运载

tasks:进程总数,运行的进程数,睡眠的进程数,停止的进程数,僵尸进程数

S:进程的状态,有以下不同的值

  • D:不可中断的睡眠态
  • R:运行态
  • S:睡眠态
  • T:被跟踪或者已经停止
  • Z:僵尸态

孤儿进程:父进程退出了,子进程还在运行的进程,这些进程被init进程(进程号为1)收养,由init进程对他们完成状态收集工作

僵尸进程:进程使用fork创建子进程,如果子进程退出,父进程没有调用wait或者waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这些进程就是僵尸进程

避免僵尸进程的方法:fork两次用孙子进程区完成子进程的任务,用wait函数使得父进程阻塞,使用信号量在signal handler中调用waitpid,这样父进程不用阻塞 

park unpark和wait notify的区别?

wait和notify使用的时候有很多的限制:

  • 必须在synchronized同步块中使用,因为他们依赖于synchronized里面的锁
  • 不能指定唤醒哪一个线程,只能随机的返回一个线程
  • 调用wait,虽然当前线程还在同步块中,由于不满足运行条件,调用wait方法,使得线程在等待队列中等待,当其他线程调用notify的时候,它又从等待队列中唤醒,参与锁的竞争,这个时候可能会满足运行条件,也可能不满足运行条件

park和unpark:

  • 随时随地可以调用
  • LockSupport运行先调用unpark,后调用park,a线程调用unpark(b),b线程调用park,线程b是不会阻塞的,如果线程1先调用notify,线程2再调用wait的话,线程2是会被阻塞的
  • unpark为线程t提供一个运行许可,该许可不允许重叠,只能最多持有一个

ps命令的常用参数

  • -a:显示同一终端下所有程序
  • -m:显示所有线程
  • -p:pid进程使用cpu的时间
  • -e:显示所有进程,环境变量
  • -f:全格式
  • -aux:显示所有包含其他使用者的进程
  • -au:显示较为详细的咨询

synchronized和reentrentLock的区别

synchronized是java内置的同步机制,是关键字,可以修饰方法,也可以用在特定代码块上;后者是Lock的实现类,它提供了可中断锁的等待,锁的定时等待和公平锁的选项特性

两者性能方面,在synchronized锁优化以前,reentrentLock性能优于synchronized,优化后两者性能差不太多,由于synchronized提供了偏向锁,轻量级cas锁的功能

redis数据结构set的底层是什么?

底层使用了intset和hashtable两种数据结构存储,intset可以理解为数组,hashtable就是普通的哈希表,set的底层存储intset和hashtable是存在编码转换的,使用intset存储必须满足下面两个条件,否则使用hashtable

  • 集合对象所有元素都是整数值
  • 集合对象保存的元素数量不超过512

inset其实就是一个有序的无重复数组

SortedSet底层是字典+跳跃表,字典的key是集合元素,value是对应分值,跳跃表按照分值value进行排序,方便定位成员

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值