JAVA面试题集合
面试题: 1. HashMap、Hashtable、ConcurrentHashMap、LinkedHashMap的区别 HashMap
HashMap是基于哈希表的Map接口的非同步实现, 允许使用null值和null键(HashMap最多只允许一条记录的键为null,允许多条记录的值为null。)。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap中不允许出现重复的键(Key) Hashmap是非线程安全的, 其迭代器是fail-fast的 HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体,(JDK1.8增加了红黑树部分,会将时间复杂度从O(n)降为O(logn))。 数据存储:先根据key的hashCode(使用key的hashCode()方法获取)重新计算hash值,根据hash值算出这个元素在数组中的位置(即下标), 如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。 数据读取:首先根据key的hashCode,找到其数组中对应位置的数据(可能只有一个数据,也可能是多个数据,其表现形式是一个链表),然后通过key的equals方法在对应位置的链表中找到需要的元素。 hashMap的默认初始容量是16个,其会有一个负载因子,用于当hashMap中的数据量等于容量*负载因子时,hashMap会进行扩容,扩大的容量是原本的2倍。负载因子的默认初始值为0.75
Hashtable
Hashtable也是一个散列表,它存储的内容是键值对。基于Dictionary类 存储数据: 首先判断value是否为空,为空则抛出异常;计算key的hash值,并根据hash值获得key在table数组中的位置index,如果table[index]元素不为空,则进行迭代,如果遇到相同的key,则直接替换,并返回旧value;否则,我们可以将其插入到table[index]位置。 key和value都不允许为null,Hashtable遇到null,直接返回NullPointerException。 线程安全,几乎所有的public的方法都是synchronized的 较HashMap速度慢 LinkedHashMap
LinkedHashMap是HashMap的一个子类,它保留插入顺序,帮助我们实现了有序的HashMap。 其维护一个双向链表,并不是说其除了维护存入的数据,另外维护了一个双向链表对象,而是说其根据重写HashMap的实体类Entry,来实现能够将HashMap的数据组成一个双向列表,其存储的结构还是数组+链表的形式, LinkedHashMap能够做到按照插入顺序或者访问顺序进行迭代顺序。 修改Entry对象,Entry新增了其上一个元素before和下一个元素after的引用 根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表 LinkedHashMap并未重写父类HashMap的put方法,只是重写了put方法里面的recordAccess、addEntry、createEntry等方法,添加了特有的双向链接列表 LinkedHashMap重写了父类HashMap的get方法,实际在调用父类getEntry()方法取得查找的元素后,再判断当排序模式accessOrder为true时,记录访问顺序,将最新访问的元素添加到双向链表的表头,并从原来的位置删除。由于的链表的增加、删除操作是常量级的,故并不会带来性能的损失。 这个虽然是访问元素,但是当设置以访问排序时,其仍然会先将元素从原本位置remove掉,然后在将该元素以新元素插入到链头,哪怕其新插入的位置还在原位置(所以如果以访问排序,其过程会涉及到删除数据和增添数据)。 读取速度与容量无关
ConcurrentHashMap
ConcurrentHashMap是弱一致性,也就是说遍历过程中其他线程可能对链表结构做了调整,因此get和containsKey返回的可能是过时的数据 ConcurrentHashMap是基于分段锁设计来实现线程安全性,只有在同一个分段内才存在竞态关系,不同的分段锁之间没有锁竞争。 并发度就是ConcurrentHashMap中的分段锁个数,默认的并发度为16,ConcurrentHashMap会使用大于等于该值的最小2幂指数作为实际并发度(假如用户设置并发度为17,实际并发度则为32) 通过将key的高n位(n = 32 – segmentShift)和并发度减1(segmentMask)做位与运算定位到所在的Segment(分段)
2. HashMap的底层实现原理 HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
因为HashMap的好处非常多,我曾经在电子商务的应用中使用HashMap作为缓存。因为金融领域非常多的运用Java,也出于性能的考虑,我们会经常用到HashMap和ConcurrentHashMap。
2.Throw 和 throws的区别
1.1 throw是语句抛出一个异常。 语法:throw (异常对象); throw e;
1.2 throws是方法可能抛出异常的声明 用在声明方法时,表示该方法可能要抛出编译时的异常,如果某个异常是编译是异常,那么这个异常就需要捕获(try{}catch{}finally{}) 或者抛出(throws)。否者的话是无法通过编译的。 语法:(修饰符)(方法名)([参数列表])[throws(异常类)]{…} public void doA(int a) throws Exception1,Exception3{…}
3.Final、finalize –Object、finally的区别
1.final 这个关键字在Java中代表不可改变的,可用来修饰类,方法,变量。 对class来说,用法如:final class A{}代表类A为终极类,不能被继承,也就没有子类之说与接口实现者之说。因此一个类不能既被声明为 abstract,又被声明为final。 对method来说,用法如:public final void test(){} 代表此方法不能被重写。 对variable来说,必须在声明时赋值,且不能再改变。
2.finally finally关键字用在异常处理中,用于处理异常后的清理工作,实际中一般用于关闭文件流,释放资源等操作。
3.finalize finalize一方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,它是在 Object 类中定义的,因此所有的类都继承了它,子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。
4.JDK1.8的新特性:lambda表达式,stream,default lambda表达式: Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。 (parameters) -> expression (parameters) ->{ statements; } “ ->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分: 左侧: 指定了 Lambda 表达式需要的所有参数 右侧: 指定了 Lambda 体,即 Lambda 表达式要执行的功能。 函数式接口: Lambda 需要 函数式接口的支持 函数式接口:这个接口中,仅仅只有 一个抽象方法 函数式接口可以使用注解 @FunctionalInterface 来检测这个接口是不是函数式接口 Stream API: Stream API 提供了一种高效且易于使用的处理数据的方式,使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。 Stream 流: 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
作者:清风明月… 来源: 原文:https://blog.csdn.net/qfmy__/article/details/90756815 版权声明:本文为博主原创文章,转载请附上博文链接!
default 接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现。 如下图所示,我想在Fly接口类中定义了一个方法并给了具体的方法体,IDEA给出的提示是 1、将该方法定义为静态方法。 2、为该方法加上default关键字。
default方法是在java8中引入的关键字,(也就是接口中可以包含方法体,这打破了Java之前版本对接口的语法限制),从而使得接口在进行扩展的时候,不会破坏与接口相关的实现类代码。
作者:gkmmg 来源: 原文:https://blog.csdn.net/gkmmg/article/details/79656267 版权声明:本文为博主原创文章,转载请附上博文链接!
5. 对线程的理解? • 一个线程时进程的一个顺序执行流。 • 同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。 • 一个进程中可以包含多个线程。 与进程的区别 • 一个进程至少有一个线程。 • 线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大的提高了程序的运行效率。 • 线程在执行过程中与进程的区别在于每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 • 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看作多个独立的应用来实现进程的调度和管理以及资源分配。
6. 线程有哪些状态 线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
1.新建状态 当用new操作符创建一个线程时。此时程序还没有开始运行线程中的代码。
2.就绪状态 一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序来调度的。
3.运行状态(running) 当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法。
4.阻塞状态(blocked) 线程运行过程中,可能由于各种原因进入阻塞状态:
①线程通过调用sleep方法进入睡眠状态;
②线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
③线程试图得到一个锁,而该锁正被其他线程持有;
④线程在等待某个触发条件;
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
5.死亡状态(dead) 有两个原因会导致线程死亡:
①run方法正常退出而自然死亡;
②一个未捕获的异常终止了run方法而使线程猝死;
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法,如果是可运行或被阻塞,这个方法返回true;如果线程仍旧是new状态且不是可运行的,或者线程死亡了,则返回false。
7. Runnable 和 Callable的区别 两者区别主要以下两点
1.Runnable接口的run()方法没有返回值,而Callable接口的call()方法可以有返回值。 2.Runnable接口的run()方法不可以声明抛出异常,而Callable接口的call()方法可以声明抛出异常。
8. 线程同步(排队执行)、线程异步(多个线程抢占资源)的定义、什么时候会产生线程安全问题?
线程同步:是多个线程同时访问同一资源,等待资源访问结束才能进行下一个线程,浪费时间,效率低
线程异步:访问资源时在空闲等待时同时访问其他资源,实现多线程机制
异步处理就是,你现在问我问题,我可以不回答你,等我有时间了再处理你这个问题.同步反之
同步的好处与弊端 好处:解决了线程的安全问题。 弊端:每次都有判断锁,降低了效率。
同步的前提 一、多个线程执行的时候需要同步,如果是单线程则不需要同步。 二、多个线程在执行的过程中是不是使用同一把锁。如果是,就是同步。否则不是同步
线程不安全: 是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。即数据处理出现意外指。 比如两个线程去买票抢占同一资源时导致程序逻辑出现错误 我们来分析一下这种结果是怎么产生的,假设第一个线程执行完成 account.getBalance()>=drawAmount 这行代码后,cpu将它停止,切换到第二线程去执行account.getBalance()>=drawAmount。这个时候内存中的account还是1000, 因为第一个线程没有执行完成account.setBalance( (account.getBalance()-drawAmount))去修改account值。所以第二个线程继续还是进入了取钱的代码块。这个时候其实逻辑上已经出错了,所以当前两个线程都减去800的时候就会出现负值。这就是多线程的安全问题。
9. 怎么解决线程安全问题 线程安全是通过线程同步控制来实现的,也就是synchronized关键字。(上锁) 多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。
10.HashMap的底层实现原理 简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
详细 原文:https://blog.csdn.net/qq_40118851/article/details/82804510
11.Java线程、锁 – 原理 synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象 静态同步方法,锁是当前类的class对象 同步方法块,锁是括号里面的对象 当一个线程访问同步代码块时,它首先是需要得到锁才能执行同步代码,当退出或者抛出异常时必须要释放锁,
几种锁的种类 ·公平锁/非公平锁 ·可重入锁 ·独享锁/共享锁 ·互斥锁/读写锁 ·乐观锁/悲观锁 ·分段锁 ·偏向锁/轻量级锁/重量级锁 ·自旋锁 上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。
公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁。 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。 对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。 对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。
可重入锁 可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。 对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。 对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
synchronized void setA() throws Exception{ Thread.sleep(1000); setB(); }
synchronized void setB() throws Exception{ Thread.sleep(1000); } 上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。
独享锁/共享锁 独享锁是指该锁一次只能被一个线程所持有。 共享锁是指该锁可被多个线程所持有。
对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。 读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。 独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。 对于Synchronized而言,当然是独享锁。
互斥锁/读写锁 上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。 互斥锁在Java中的具体实现就是ReentrantLock 读写锁在Java中的具体实现就是ReadWriteLock
乐观锁/悲观锁 乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。 悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。 乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。
从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。 悲观锁在Java中的使用,就是利用各种锁。 乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。
分段锁 分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。 我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。 当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。 但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。 分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
偏向锁/轻量级锁/重量级锁 这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。 轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。 重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
自旋锁 在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。 典型的自旋锁实现的例子,可以参考自旋锁的实现
12.线程sleep和wait的区别。 第一种解释:
功能差不多,都用来进行线程控制,他们最大本质的区别是:sleep()不释放同步锁,wait()释放同步缩.
还有用法的上的不同是:sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间不到你只能调用interreput()来强行打断;wait()可以用notify()直接唤起.
第二种解释:
sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行,例如: try{ System.out.println(“I’m going to bed”); Thread.sleep(1000); System.out.println(“I wake up”); } catch(IntrruptedException e) { } wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者,例如: //Thread 1
try{ obj.wait();//suspend thread until obj.notify() is called } catch(InterrputedException e) { }
第三种解释:
这两者的施加者是有本质区别的. sleep()是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定.好比如说,我要做的事情是 “点火->烧水->煮面”,而当我点完火之后我不立即烧水,我要休息一段时间再烧.对于运行的主动权是由我的流程来控制.
支持一下吆 收藏一下: 很好
而wait(),首先,这是由某个确定的对象来调用的,将这个对象理解成一个传话的人,当这个人在某个线程里面说"暂停!",也是 thisOBJ.wait(),这里的暂停是阻塞,还是"点火->烧水->煮饭",thisOBJ就好比一个监督我的人站在我旁边,本来该线 程应该执行1后执行2,再执行3,而在2处被那个对象喊暂停,那么我就会一直等在这里而不执行3,但正个流程并没有结束,我一直想去煮饭,但还没被允许, 直到那个对象在某个地方说"通知暂停的线程启动!",也就是thisOBJ.notify()的时候,那么我就可以煮饭了,这个被暂停的线程就会从暂停处 继续执行.
其实两者都可以让线程暂停一段时间,但是本质的区别是一个线程的运行状态控制,一个是线程之间的通讯的问题
在java.lang.Thread类中,提供了sleep(), 而java.lang.Object类中提供了wait(), notify()和notifyAll()方法来操作线程 sleep()可以将一个线程睡眠,参数可以指定一个时间。 而wait()可以将一个线程挂起,直到超时或者该线程被唤醒。 wait有两种形式wait()和wait(milliseconds). sleep和wait的区别有: 1,这两个方法来自不同的类分别是Thread和Object 2,最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 3,wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在 任何地方使用 synchronized(x){ x.notify() //或者wait() } 4,sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
作者:shliuzw 来源: 原文:https://blog.csdn.net/liuzhenwen/article/details/4202967 版权声明:本文为博主原创文章,转载请附上博文链接!
13.线程池的好处、意义、优势、使用线程池的必要性 四,线程池
又以上介绍我们可以看出,在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
那么,我们应该如何创建一个线程池那?Java中已经提供了创建线程池的一个类:Executor
而我们创建时,一般使用它的子类:ThreadPoolExecutor.
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 这是其中最重要的一个构造方法,这个方法决定了创建出来的线程池的各种属性,下面依靠一张图来更好的理解线程池和这几个参数:
又图中,我们可以看出,线程池中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收,maximumPoolSize就是线程池中可以容纳的最大线程的数量,而keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间,而util,就是计算这个时间的一个单位,workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory,就是创建线程的线程工厂,最后一个handler,是一种拒绝策略,我们可以在任务满了知乎,拒绝执行某些任务。
线程池的执行流程又是怎样的呢?
有图我们可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。
handler的拒绝策略:
有四种:第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
第二种DisCardPolicy:不执行新任务,也不抛出异常 第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行 第四种CallerRunsPolicy:直接调用execute来执行当前任务五,四种常见的线程池:
CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
14.TCP/IP协议是什么,有什么作用,有几层 TCP/IP协议分为4个层次,自上而下依次为应用层、传输层、网络层、网络接口层。 各层的功能如下:
1、应用层的功能为对客户发出的一个请求,服务器作出响应并提供相应的服务。
2、传输层的功能为通信双方的主机提供端到端的服务,传输层对信息流具有调节作用,提供可靠性传输,确保数据到达无误。
3、网络层功能为进行网络互连,根据网间报文IP地址,从一个网络通过路由器传到另一网络。
4、网络接口层负责接收IP数据报,并负责把这些数据报发送到指定网络上。
15.TCP/IP协议三次握手有什么用? TCP传输控制协议,是一个面向连接的协议。在TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的。所谓三次握手是指建立一个 TCP 连接时需要客户端和服务器端总共发送三个包以确认连接的建立。三次握手的目的是同步连接双方的序列号和确认号并交换 TCP窗口大小信息。
第一次握手:客户端发送一个TCP标志位SYN=1,ACK=0的数据包给服务端,并随机会产生一个Seq=J.当服务端接收到这个数据后,服务端由SYN=1可知客户端是想要建立连接;
第二次握手:服务端要对客户端的联机请求进行确认,向客户端发送应答号ACK=1、SYN=1 确认号Ack=J+1,此值是客户端的序列号加1,还会产生一个随机的序列号Seq=K,这样就告诉客户端可以进行连接;
第三次握手:客户端收到数据后检查Ack是否为J+1,以及标志位ACK的值是否为1,若为1,则会发送ACK=1、确认号码Ack=K+1,告诉服务端,你的请求连接被确认,连接可以建立,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。