zoukankan      html  css  js  c++  java
  • 面试基本概念原理最佳实践

    1) Java中什么是竞态条件? 举个例子说明。

    竞态条件会导致程序在并发情况下出现一些bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了, 那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争。一个例子就是无序处理

    竞态条件(Race Condition):计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。

    最常见的竞态条件为:

    一,先检测后执行。执行依赖于检测的结果,而检测结果依赖于多个线程的执行时序,而多个线程的执行时序通常情况下是不固定不可判断的,从而导致执行结果出现各种问题。

    对于main线程,如果文件a不存在,则创建文件a,但是在判断文件a不存在之后,Task线程创建了文件a,这时候先前的判断结果已经失效,(main线程的执行依赖了一个错误的判断结果)此时文件a已经存在了,但是main线程还是会继续创建文件a,导致Task线程创建的文件a被覆盖、文件中的内容丢失等等问题。

    多线程环境中对同一个文件的操作要加锁。

    和大多数并发错误一样,竞态条件不总是会产生问题,还需要不恰当的执行时序 

     

    2)Java中有哪些实现并发编程的方法

    1. synchronized关键字

    2. 使用继承自Object类的wait、notify、notifyAll方法
    3. 使用线程安全的API和集合类:

    • 使用Vector、HashTable等线程安全的集合类
    • 使用Concurrent包中提供的ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue等弱一致性的集合类
    • 在Collections类中有多个静态方法,它们可以获取通过同步方法封装非同步集合而得到的集合,如List list = Collection.synchronizedList(new ArrayList())。

    4. 使用原子变量、volatile变量等
    5. 使用Concurrent包中提供的信号量Semaphore、闭锁Latch、栅栏Barrier、交换器Exchanger、Callable&Future、阻塞队列BlockingQueue等.
    6. 手动使用Lock实现基于锁的并发控制
    7. 手动使用Condition或AQS实现基于条件队列的并发控制
    8. 使用CAS和SPIN等实现非阻塞的并发控制

      

    3)进程间通信的几种方式

    1. 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

    2. 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

    3. 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

    4. 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

    5. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

    6. 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

    7. 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

    4)ConcurrentHashMap

    ConcurrentHashMap是线程安全的HashMap,内部采用分段锁来实现,默认初始容量为16,装载因子为0.75f,分段16,每个段的HashEntry<K,V>[]大小为2。键值都不能为null。每次扩容为原来容量的2倍,ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容。在获取size操作的时候,不是直接把所有segment的count相加就可以可到整个ConcurrentHashMap大小,也不是在统计size的时候把所有的segment的put, remove, clean方法全部锁住,这种方法太低效。在累加count操作过程中,之前累加过的count发生变化的几率非常小,所有ConcurrentHashMap的做法是先尝试2(RETRIES_BEFORE_LOCK)次通过不锁住Segment的方式统计各个Segment大小,如果统计的过程中,容器的count发生了变化,再采用加锁的方式来统计所有的Segment的大小。

    5)ABA问题

    ABA问题发生在类似这样的场景:线程1转变使用CAS将变量A的值替换为C,在此时,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍为A,所以CAS成功。但实际上这时的现场已经和最初的不同了。大多数情况下ABA问题不会产生什么影响。如果有特殊情况下由于ABA问题导致,可用采用AtomicStampedReference来解决,原理:乐观锁+version。可以参考下面的案例来了解其中的不同

     1 public class ABAQuestion
     2 {
     3     private static AtomicInteger atomicInt = new AtomicInteger(100);
     4     private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100,0);
     5  
     6     public static void main(String[] args) throws InterruptedException
     7     {
     8         Thread thread1 = new Thread(new Runnable(){
     9             @Override
    10             public void run()
    11             {
    12                 atomicInt.compareAndSet(100, 101);
    13                 atomicInt.compareAndSet(101, 100);
    14             }
    15         });
    16  
    17         Thread thread2 = new Thread(new Runnable(){
    18             @Override
    19             public void run()
    20             {
    21                 try
    22                 {
    23                     TimeUnit.SECONDS.sleep(1);
    24                 }
    25                 catch (InterruptedException e)
    26                 {
    27                     e.printStackTrace();
    28                 }
    29                 boolean c3 = atomicInt.compareAndSet(100, 101);
    30                 System.out.println(c3);
    31             }
    32         });
    33  
    34         thread1.start();
    35         thread2.start();
    36         thread1.join();
    37         thread2.join();
    38  
    39         Thread thread3 = new Thread(new Runnable(){
    40             @Override
    41             public void run()
    42             {
    43                 try
    44                 {
    45                     TimeUnit.SECONDS.sleep(1);
    46                 }
    47                 catch (InterruptedException e)
    48                 {
    49                     e.printStackTrace();
    50                 }
    51                 atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
    52                 atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
    53             }
    54         });
    55  
    56         Thread thread4 = new Thread(new Runnable(){
    57             @Override
    58             public void run()
    59             {
    60                 int stamp = atomicStampedRef.getStamp();
    61                 try
    62                 {
    63                     TimeUnit.SECONDS.sleep(2);
    64                 }
    65                 catch (InterruptedException e)
    66                 {
    67                     e.printStackTrace();
    68                 }
    69                 boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1);
    70                 System.out.println(c3);
    71             }
    72         });
    73         thread3.start();
    74         thread4.start();
    75     }
    76 }
    AtomicStampedRefrence

    输出结果:

    true
    false
    View Code

    6)servlet是线程安全的吗

    Servlet不是线程安全的。要解释为什么Servlet为什么不是线程安全的,需要了解Servlet容器(即Tomcat)是如何响应HTTP请求的。

    当Tomcat接收到Client的HTTP请求时,Tomcat从线程池中取出一个线程,之后找到该请求对应的Servlet对象并进行初始化,之后调用service()方法。要注意的是每一个Servlet对象在Tomcat容器中只有一个实例对象,即是单例模式。如果多个HTTP请求请求的是同一个Servlet,那么着两个HTTP请求对应的线程将并发调用Servlet的service()方法。

     
    上图中的Thread1和Thread2调用了同一个Servlet1,所以此时如果Servlet1中定义了实例变量或静态变量,那么可能会发生线程安全问题(因为所有的线程都可能使用这些变量)

    比如下面的Servlet中的 name 和 i变量就会引发线程安全问题。

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class ThreadSafeServlet extends HttpServlet {
    
        public static String name = "Hello";   //静态变量,可能发生线程安全问题
        int i;  //实例变量,可能发生线程安全问题
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    
        @Override
        public void init() throws ServletException {
            super.init();
            System.out.println("Servlet初始化");
        }
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.printf("%s:%s[%s]
    ", Thread.currentThread().getName(), i, format.format(new Date()));
            i++;
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.printf("%s:%s[%s]
    ", Thread.currentThread().getName(), i, format.format(new Date()));
            resp.getWriter().println("<html><body><h1>" + i + "</h1></body></html>");
        }
    }


    9)linux/unix下统计一行字符串出现指定字符的次数

    一种方案: 先把 | 替换成换行符,wc -l 统计时候的的数量就一定是result + 1   最后把1减掉

    unix系统用 sed 's/|/ /g' 无法替换(linux 可以),所以考虑用tr替换

    echo $(echo "10.1.4.90|15/Jan/2018:08:44:38|GET|pi.easyond/|" | tr '|' ' ' | wc -l)-1 | bc  

    10)快速失败(fail - fast)和安全失败(fail - safe)

    一 快速失败 fail-fast

     在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加,删除,修改), 则会抛出 ConcurrentModificationException

    原理: 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值,每当迭代器使用hasNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedModCount值,是的话就返回遍历,否则抛出异常,终止遍历。

    下面以AbstractList的实现举例,其他原理同,不再累述

    注意:这里异常的抛出条件是检测到this.modCount != l.modCount, 如果集合发生变化时又恰好修改了modCount的值使上述条件语句满足,那么异常不会抛出。因此,不能依赖这个异常是否抛出的逻辑进行并发操作的编程,这个异常只建议用于检测并发修改的bug

    场景 java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)

    二 安全失败 fail-safe

    采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历

    原理: 由于迭代时时对原集合的拷贝进行遍历,所以在遍历过程中对原集合所做的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException

    缺点: 基于拷贝内容的优点是避免了ConcurrentModificationException,但相对地,迭代器并不能访问到修改后的内容。即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器无从得知。

    场景: java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

     

    11)mysql的undo和redo

    http://www.cnblogs.com/Bozh/archive/2013/03/18/2966494.html
     
     
     

    12)cookie和session

    http://blog.csdn.net/gavin_john/article/details/51376364
     
     

    13)网站短链接生成算法

    https://www.zhihu.com/question/29270034
     
     
     

    14)linux下kill -9 pid强制不能杀掉进程原因 

    kill -9发送SIGKILL信号将其终止,但是以下两种情况不起作用:
    a、该进程处于"Zombie"状态(使用ps命令返回defunct的进程)。此时进程已经释放所有资源,但还未得到其父进程的确认。"zombie"进程要等到下次重启时才会消失,但它的存在不会影响系统性能。
    b、 该进程处于"kernel mode"(核心态)且在等待不可获得的资源。处于核心态的进程忽略所有信号处理,因此对于这些一直处于核心态的进程只能通过重启系统实现。进程在AIX 中会处于两种状态,即用户态和核心态。只有处于用户态的进程才可以用“kill”命令将其终止。 

    用top命令查看发现zombie进程数是0,看来这三个进程不属于僵尸进程,应该是b这中情况,就是这些进程进入核心态等待磁盘资源时出现磁盘空间不足的故障,这时我强制关闭了数据库,所以这几个进程就一直处于核心态无法被杀除,看来只能重启了。
     
     

    15)给定a,b两个文件,各存放50亿个url,每个url各占用64字节,内存限制是4G,如果找出a,b文件共同的url

     思路:可以估计每个文件的大小为5G*64=300G,远大于4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。 遍历文件a,对每个url求取hash(url)%1000,然后根据所得值将url分别存储到1000个小文件(设为a0,a1,...a999)当中。这样每个小文件的大小约为300M。遍历文件b,采取和a相同的方法将url分别存储到1000个小文件(b0,b1....b999)中。这样处理后,所有可能相同的url都在对应的小文件(a0 vs b0, a1 vs b1....a999 vs b999)当中,不对应的小文件(比如a0 vs b99)不可能有相同的url。然后我们只要求出1000对小文件中相同的url即可。 比如对于a0 vs b0,我们可以遍历a0,将其中的url存储到hash_map当中。然后遍历b0,如果url在hash_map中,则说明此url在a和b中同时存在,保存到文件中即可。 如果分成的小文件不均匀,导致有些小文件太大(比如大于2G),可以考虑将这些太大的小文件再按类似的方法分成小小文件即可

    16)100亿个url中的重复url已经搜索词汇的topK问题

    有一个包含100亿个URL的文件,假设每个URL占用64B,请找出其中所有重复的URL。

    将文件通过哈希函数成多个小的文件,由于哈希函数所有重复的URL只可能在同一个文件中,在每个文件中利用一个哈希表做次数统计。就能找到重复的URL。这时候要注意的就是给了多少内存,我们要根据文件大小结合内存大小决定要分割多少文件

    topK问题和重复URL其实是一样的,重复的多了才会变成topK。其实就是在上述方法后获得所有的重复URL排个序,但是有点没必要,因为我们要找topK时,最极端的情况也就是topK在用一个文件中,所以我们只需要每个文件的topK个URL,之后再进行排序,这样就比找出全部的URL再排序好。还有一个topK个URL到最后还是需要排序,所以我们在找每个文件的topK时,是否只需要找到topK个,其它顺序不用管,那么我们就可以用大小为K的小根堆遍历哈希表。这样又可以降低查找的时间。

    17)如何判断服务器需要扩容

    有很多指标,最基本的比如CPU利用率,内存占用率,网络带宽等。软件层面比如QPS等。

    容量设计的时候需要考虑突发流量,一般要保留20%到30%的备用容量。

    集群还要考虑灾备,比如部分机器发生故障的时候接负载均衡如何正确引流限流,避免雪崩式故障(cascading failure)。

    至于工具的话,要看现有的技术框架。主要的目的就是做好实时监控和预警。

    18)linux下如何查看进行占用CPU和内存的情况

    1 ps 

    按内存大小排序(%MEM为第三列,%CPU为第二列,不在雷述)

    ps -aux默认是按PID从小到大显示的,若想按占用内存大小排序则需要借助另外sort命令针对第4列(内存所在列)排序:

    ps -aux | sort -k4rn
    
    

    我们还可以借助awk来指定显示哪几列信息:

    ps -aux |awk '{print $2, $4, $11}' | sort -k2rn | head -n 10
    
    

    2 top 

    在命令行提示符执行top命令.输入大写P,则结果按CPU占用降序排序。输入大写M,结果按内存占用降序排序

      

    20)Arrays.sort() 和Collections.sort() 底层是用什么排序

    1 Arrays.sort()

    public static void sort(int[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }

    DualPivotQuicksort翻译过来就是双轴快速排序,再点进去发现有这样的判断

    if (right - left < QUICKSORT_THRESHOLD) {
        sort(a, left, right, true);
        return;
    }

    可以发现如果数组的长度小于QUICKSORT_THRESHOLD的话就会使用这个双轴快速排序,而这个值是286。

     那如果大于286,它会检测数组的连续上升/下降的特性好不好,好的话用归并排序,坏的话用快排

    再回到上面的双轴快速排序,点进去

    sort(int[] a, int left, int right, boolean leftmost)这个方法发现
    如果数据长度小于INSERTION_SORT_THRESHOLD(值为47)的话,就会用插入排序

    总结:

    长度大于等于286且连续性好->归并

    长度大于等于286且连续性不好->双轴快速排序

    长度小于286且大于等于47->双轴快速排序

    长度小于47->插入排序

    2 Collections.sort()

    一路点进去发现到了Arrays类中

    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

    会发现如果LegacyMergeSort.userRequested为true的话就会使用归并排序,可以通过下面代码设置为true

    System.setProperty("java.util.Arrays.useLegacyMergeSort", "true"); 

    不过方法legacyMergeSort的注释上有这么一句话,说明以后传统归并可能会被移除了。

    /** To be removed in a future release. */

    如果不为true的话就会用一个叫TimSort的排序算法。

    21)Object中有哪些方法

    public方法: getClass()  hashCode()  equals()  toString()  notify()系列  wait()系列

    protected方法: clone() finalize()

    private方法: registerNatives, 该方法作用是将不同平台c/c++实现的方法映射到java的native方法

        private static native void registerNatives();
        static {
            registerNatives();
        }

    22)jvm GC根节点的选择

    java通过可达性分析来判断对象是否存活,基本思想是通过一系列称为"GC roots"的对象作为起始点,可以作为根节点的是:

    (1) 虚拟机栈(栈帧中的本地变量表)中引用的对象

    (2) 本地方法栈中JNI(即native方法)引用的对象

    (3) 方法区中静态属性引用的对象

    (4) 方法区中常量引用的对象

    作为GC roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中。

    我个人理解: 想下java内存模型的5块,gc主要是收集堆和持久代,恰好对应栈分配的堆对象和静态&常量所申请的持久代

    23)类加载主要步骤

    • 加载 把 class 文件的二进制字节流加载到 jvm 里面
    • 验证 确保 class 文件的字节流包含的信息符合当前 jvm 的要求 有文件格式验证, 元数据验证, 字节码验证, 符号引用验证等
    • 准备 正式为类变量分配内存并设置类变量初始值的阶段, 初始化为各数据类型的零值
    • 解析 把常量值内的符号引用替换为直接引用的过程
    • 初始化 执行类构造器()方法
    • 使用 根据相应的业务逻辑代码使用该类
    • 卸载 类从方法区移除

    24)Class.forName和classloader的区别

    java中class.forName()和classLoader都可用来对类进行加载。
    class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
    而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。 

    25)获取class类的四种方式

    1.调用运行时类本身的.class属性

    Class clazz = String.class;

    2,通过运行时类的对象获取

    Person p = new Person();

    Class clazz = p.getClass();

    3.通过Class的静态方法获取:体现反射的动态性

    String className = “java.util.commons”;

    Class clazz = Class.forName(className);

    4.通过类的加载器

    String className = “java.util.commons”;

    ClassLoader classLoader = this.getClass().getClassLoader();

    Class clazz = classLoader.loadClass(className);

    26)String为什么是final的

    final的出现是为了不想改变,而不想改变的理由有两点: 设计(安全)或者效率

    1 设计安全

    确保不会在子类中改变语义。如果有一个String的引用,它引用的一定是一个string对象,而不可能是其他类的对象

    2 效率

    设计成final,jvm不用对相关方法在虚函数表中查询,而直接定位到String类的相关方法,提高了执行效率

     一旦创建是不能被修改的。字符串对象是不可改变的,那么它们可以共享

    27)kafka等mq如何保证有序性

    传统的队列在服务器上保存有序的消息,如果多个consumers同时从这个服务器消费消息,服务器就会以消息存储的顺序向consumer分发消息。虽然服务器按顺序发布消息,但是消息是被异步的分发到各consumer上,所以当消息到达时可能已经失去了原来的顺序,这意味着并发消费将导致顺序错乱。为了避免故障,这样的消息系统通常使用“专用consumer”的概念,其实就是只允许一个消费者消费消息,当然这就意味着失去了并发性。

    在这方面Kafka做的更好,通过分区的概念,Kafka可以在多个consumer组并发的情况下提供较好的有序性和负载均衡。将每个分区分只分发给一个consumer组,这样一个分区就只被这个组的一个consumer消费,就可以顺序的消费这个分区的消息。因为有多个分区,依然可以在多个consumer组之间进行负载均衡。注意consumer组的数量不能多于分区的数量,也就是有多少分区就允许多少并发消费。

    Kafka只能保证一个分区之内消息的有序性,在不同的分区之间是不可以的,这已经可以满足大部分应用的需求。如果需要topic中所有消息的有序性,那就只能让这个topic只有一个分区,当然也就只有一个consumer组消费它。

    28)java对象引用方式 - 强引用,软引用,弱引用,虚引用

    从jdk1.2版本开始,把对应的引用分为4种级别,由高到低分别为: 强引用 > 软引用 > 弱引用 > 虚引用.

    (1) 强引用

    这是最普遍的引用。当内存空间不足时,java虚拟机宁愿抛出OutOfMemoryError使程序异常终止,也不会随意回收具有强引用的对象来解决内存不足问题

    (2) 软引用

    软引用在内存空间足够时,垃圾回收器不会进行回收。但内存不足时就会进行回收这些对象的内存。软引用可用来实现内存敏感的高速缓存

    (3) 弱引用

    具有比软引用更短的生命周期,在垃圾回收器线程扫描它所管辖的内存区域时,一旦有弱引用,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器时一个优先级很低的线程,因此不一定会发现那些只具有弱引用的对象

    (4)虚引用

    顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

    特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

    100)其他

    (2) spring中的九种设计模式: http://ju.outofmemory.cn/entry/78206
      (5) 美团点评技术团队-swap过高分析linux与jvm的内存关系(写的灰常好)   http://www.importnew.com/14486.html
    (6) happens-before在java源码中的应用(CopyOnWriteArrayList & FutureTask) http://ifeve.com/easy-happens-before/
      (7) 全局唯一自增id生成策略  https://www.cnblogs.com/baiwa/p/5318432.html
    (8) Semaphore,可以整理一下,没什么难度,主要是流量控制  https://www.jianshu.com/p/0090341c6b80
    (10) hashmap1.7&1.8     http://www.importnew.com/20386.html
    (12) 红黑树                  http://blog.csdn.net/u011240877/article/details/53329023
      (14) 删除list的元素的各种办法对比 http://blog.csdn.net/claram/article/details/53410175  

    (16)分类清晰的简单面试题 https://juejin.im/entry/5838f9f1128fe1006bdaa456  

    (17)tcp粘包  https://www.cnblogs.com/kex1n/p/6502002.html

    (18)lru cache的算法实现 https://www.cnblogs.com/lzrabbit/p/3734850.html

    dubbo。http://wyj.shiwuliang.com/2017/02/25/Dubbo%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%20%E2%80%94%20%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C/

    (19)单点登录sso  http://www.cnblogs.com/ywlaker/p/6113927.html

  • 相关阅读:
    爱情七十八课,闲了就“犯贱”
    阿里巴巴中文站的CSS设计规则(转)
    爱情八十一课,可预测的分手
    [性格][管理]《九型人格2》 唐·理查德·里索(美)、拉斯·赫德森(美)
    爱情八十二课,爱情三国杀
    爱情七十九课,不爱权力大
    [心理学]《爱情心灵安全岛》 四四
    一些你不知道的囧知识,保证让你崩溃
    爱情七十四课,我们的意义
    爱情七十六课,门当户对
  • 原文地址:https://www.cnblogs.com/balfish/p/6425320.html
Copyright © 2011-2022 走看看