java面试准备之协议及线程

文章目录

一、Http和Https的区别

HTTP(超文本传输协议)被用于在Web浏览器和网站服务器之间,以明文方式传递信息,不提供任何方式的数据加密,因此使用HTTP协议传输隐私信息(如:银行卡号、密码等支付信息)非常不安全。
为了解决这一安全缺陷,网景公司设计了SSL(Secure Sockets Layer)协议,在HTTP的基础上加入了SSL(Secure Sockets Layer)协议,SSL依靠SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。从而诞生了HTTPS(安全套接字层超文本传输协议)。
简单来说,HTTPS协议="SSL+HTTP协议"构建的可进行加密传输、身份认证的网络协议,是HTTP的安全版。
相同之处
大多情况下,HTTP和HTTPS是相同的,因为都是采用同一个基础的协议,作为HTTP或HTTPS客户端(浏览器),设立一个连接到Web服务器指定的端口。当服务器接收到请求,它会返回一个状态码以及消息,这个回应可能是请求信息、或者指示某个错误发送的错误信息。系统使用统一资源定位器 URI 模式,因此资源可以被唯一指定。整个过程中,唯一不同的只是一个协议头(HTTPS)的说明,其他都是一样的。
不同之处
HTTPS和HTTP的区别主要如下:
工作层:在OSI网络模型中,HTTP工作于应用层,而HTTPS工作在传输层。
连接端口:HTTP标准端口是80,而HTTPS的标准端口是443。
传输方式:HTTP是超文本传输协议,信息是明文传输,而HTTPS是SSL加密传输协议。
工作耗时:HTTP耗时=TCP握手,而HTTPS耗时=TCP握手+SSL握手。
显示形式:HTTP的URL以http://开头,而HTTPS的URL以https://开头。
费用:HTTP无需费用,而HTTPS需要到CA申请证书,一般免费证书较少,需要一定费用。
安全性:HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。
HTTPS的优点
尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个有点:
使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。
谷歌曾在2014年8月份调整搜索引擎算法,并称“比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高”。
http请求过程:
1、dns域名解析,得到对应的ip地址
2、根据ip,找到对应的服务器,发起tcp的三次握手
3、建立tcp连接后发起http请求
4、服务器响应http请求,浏览器得到html代码
5、浏览器解析html代码,并请求html代码中的资源(js、img、css等)(先得到html代码,才能去找这些资源)
6、浏览器对页面进行渲染呈现给用户
域名解析
a 首先会搜索浏览器自身的dns缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存)
b 如果浏览器自身的缓存里面没有找到,那么浏览器会搜索系统自身的dns缓存
c 如果还没有找到,那么尝试从hosts文件里面去找
d 在前面三个过程都没获取到的情况下,就递归的去域名服务器查找。
dns优化(两个方面):dns缓存、dns负载均衡

二、Get和Post的区别

1.GET在浏览器回退时是无害的,而POST会再次提交请求。
2.GET产生的URL地址可以被Bookmark,而POST不可以。
3.GET请求会被浏览器主动cache,而POST不会,除非手动设置。
4.GET请求只能进行url编码,而POST支持多种编码方式。
5.GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。、
6.GET请求在URL中传送的参数是有长度限制的,而POST么有。
7.对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
8.GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
9.GET参数通过URL传递,POST放在Request body中。
一般面试官想听的答案:
GET和POST还有一个重大区别
简单的说:
GET产生一个TCP数据包;POST产生两个TCP数据包。
长的说:
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
也就是说,GET只需要汽车跑一趟就把货送到了,而POST得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。
因为POST需要两步,时间上消耗的要多一点,看起来GET比POST更有效。这是一个坑

  1. GET与POST都有自己的语义,不能随便混用。
  2. 据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。
  3. 并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。

三、Session和cookie的区别和联系

1、cookie:
在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据。
2、session:
session和cookie的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie是存储在本地浏览器,而session存储在服务器。存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些session信息还是绰绰有余的。
3、cookie和session结合使用:
web开发发展至今,cookie和session的使用已经出现了一些非常成熟的方案。在如今的市场或者企业里,一般有两种存储方式:
(1)存储在服务端:通过cookie存储一个session_id,然后具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会把该session_id携带上来,服务器根据session_id在session库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。这种专业术语叫做server side session。
(2)将session数据加密,然后存储在cookie中。这种专业术语叫做client side session。flask采用的就是这种方式,但是也可以替换成其他形式。

四、OSI七层模型

应用层
功能:为应用程序提供服务并规定应用程序中通信相关的细节;
表示层
将应用处理的信息转换为适合网络传输的格式,或将来自下一层的数据转换为上层能够处理的格式,主要负责数据格式的转换,确保一个系统的应用层信息可被另一个系统应用层读取
超文本传输协议HTTP、文本传输协议FTP、远程登录协议TELNET、简单邮件传送协议SMTP、DNS域名解析协议、DHCP动态主机配置协议
会话层
负责建立和断开通信连接
传输层
唯一负责总体的数据传输和数据控制的一层在这一层,数据的单位称为数据段(segment)
主要功能:
1、为端到端连接提供传输服务
2、这种传输服务分为可靠和不可靠的,其中TCP是典型的可靠传输,而UDP则是不可靠传输
3、为端到端连接提供流量控制,差错控制,服务质量等管理服务
网络层
将数据传输到目标地址;主要负责寻找地址和路由选择,数据的单位称为数据包(packet)
网络层协议的代表包括:IP、IPX、RIP、OSPF等
数据链路层:
负责物理层面上的互联的、节点间的通信传输,该层的作用包括物理地址寻址、数据的成帧、流量控制、数据的检错、重发数据的单位称为帧(frame)
数据链路层协议的代表包括:ARP、RARP、SDLC、HDLC、PPP、STP、帧中继等
物理层
规定了激活、维持、关闭通信端点之间的机械特性、电气特性、功能特性以及过程特性;该层为上层协议提供了一个传输数据的物理媒体,数据的单位称为比特(bit)

五、TCP和UDP

(一)TCP与UDP基本区别

1.基于连接与无连接
2.TCP要求系统资源较多,UDP较少;
3.UDP程序结构较简单
4.流模式(TCP)与数据报模式(UDP);
5.TCP保证数据正确性,UDP可能丢包
6.TCP保证数据顺序,UDP不保证

(二)UDP应用场景:

1.面向数据报方式
2.网络数据大多为短消息
3.拥有大量Client
4.对数据安全性无特殊要求
5.网络负担非常重,但对响应速度要求高

(三)TCP三次握手

1.TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
2.TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
3.TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
4.TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
5.当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。

(四)TCP四次挥手

1.客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2.服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3.客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4.服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5.客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6.服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。(五) (五)为什么TCP客户端最后还要发送一次确认呢?
一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。

如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。

如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。

(六) 为什么客户端最后还要等待2MSL

第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

(七) 为什么建立连接是三次握手,关闭连接确是四次挥手呢

建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。

(八)如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

(九) TCP的连接建立和断开的过程,如何保证TCP发送的信息是正确的,且保证其先后顺序不被篡改?

a.为了保证数据包的可靠传递,发送方必须把已发送的数据包保留在缓冲区;
b.并为每个已发送的数据包启动一个超时定时器;
c.如在定时器超时之前收到了对方发来的应答信息(可能是对本包的应答,也可以是对本包后续包的应答),则释放该数据包占用的缓冲区;
d.否则,重传该数据包,直到收到应答或重传次数超过规定的最大次数为止。
e.接收方收到数据包后,先进行CRC校验,如果正确则把数据交给上层协议,然后给发送方发送一个累计应答包,表明该数据已收到,如果接收方正好也有数据要发给发送方,应答包也可方在数据包中捎带过去。

六、Synchronized相关问题

(一)Synchronized底层实现

Synchronized 是由 JVM 实现的一种实现互斥同步的一种方式,synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性,被 Synchronized 修饰过的程序块,在编译前后被编译器生成了monitorenter 和 monitorexit 两个字节码指令。
在虚拟机执行到 monitorenter 指令时, 首先要尝试获取对象的锁:如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数器 + 1 ;当执行 monitorexit 指令时将锁计数器 - 1 ;当计数器为 0 时,锁就被释放了。
如果获取对象失败了, 那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。
Java 中 Synchronize 通过在对象头设置标记, 达到了获取锁和释放锁的目的。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

(二)synchronized的三种应用方式

普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

(三)乐观锁的优缺点

乐观锁由CAS算法实现,CAS 具有原子性, 它的原子性由 CPU 硬件指令实现保证
乐观锁避免了悲观锁独占对象的现象, 同时也提高了并发性能, 但它也有缺点:
1.乐观锁只能保证一个共享变量的原子操作。 如果多一个或几个变量, 乐观锁将变得力不从心, 但互斥锁能轻易解决,不管对象数量多少及对象颗粒度大小。
2.长时间自旋可能导致开销大。 假如 CAS 长时间不成功而一直自旋, 会给 CPU 带来很大的开销。
3.ABA 问题。 CAS 的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过, 但这个判断逻辑不严谨, 假如内存值原来是 A,
后来被一条线程改为 B, 最后又被改成了 A, 则 CAS 认为此内存值并没有发生改变, 但实际上是有被其他线程改过的, 这种情况对依赖过程值的情景的运算结果影响很大。 解决的思路是引入版本号, 每次变量更新都把版本号加一。

(四)JVM 对 Java 的原生锁做了哪些优化?

在 Java 6 之前, Monitor 的实现完全依赖底层操作系统的互斥锁来实现, 也就是我们刚才在问题二中所阐述的获取/ 释放锁的逻辑。
由于 Java 层面的线程与操作系统的原生线程有映射关系, 如果要将一个线程进行阻塞或唤起都需要操作系统的协助, 这就需要从用户态切换到内核态来执行, 这种切换代价十分昂贵, 很耗处理器时间, 现代 JDK 中做了大量的优化。
一种优化是使用自旋锁, 即在把线程进行阻塞操作之前先让线程自旋等待一段时间, 可能在等待期间其他线程已经解锁, 这时就无需再让线程执行阻塞操作, 避免了用户态到内核态的切换。
现代 JDK 中还提供了三种不同的 Monitor 实现, 也就是三种不同的锁:
•偏向锁( Biased Locking)
•轻量级锁
•重量级锁
这三种锁使得 JDK 得以优化 Synchronized 的运行, 当 JVM 检测到不同的竞争状况时, 会自动切换到适合的锁实现, 这就是锁的升级、降级。
•当没有竞争出现时, 默认会使用偏向锁。
JVM 会利用 CAS 操作, 在对象头上的 Mark Word 部分设置线程ID, 以表示这个对象偏向于当前线程, 所以并不涉及真正的互斥锁, 因为在很多应用场景中, 大部分对象生命周期中最多会被一个线程锁定, 使用偏斜锁可以降低无竞争开销。
•如果有另一线程试图锁定某个被偏斜过的对象, JVM 就撤销偏斜锁, 切换到轻量级锁实现。
•轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁, 如果重试成功, 就使用普通的轻量级锁; 否则, 进一步升级为重量级锁。

七、ThreadLocal和volatile的作用和原理

(一)ThreadLocal

实现线程本地存储的功能 ,ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
Thread Local 为每一个线程维护变量的副本, 把共享数据的可见范围限制在同一个线程之内, 其实现原理是, 在 Thread Local 类中有一个Map, 用于存储每一个线程的变量的副本。

(二)volatile的作用和原理

被volatile修饰的变量保证Java内存模型中的可见性和有序性。
可见性:当一个线程修改了一个被volatile修饰的变量的值,新值会立即被刷新到主内存中,其他线程可以立即得知新值。有序性:禁止进行指令重排序。
volaitle底层是通过内存屏障来实现可见性和有序性。内存屏障是一个CPU的指令,他的作用有两个,一是保证特定操作的执行顺序,二是保证某些变量的内存可见性。内存屏障告诉编译器和CPU,不管什么指令都不能和这条内存屏障指令重排序,另一个作用是强制刷出各种CPU的缓存资源,因此任何CPU上的线程都能读取到这些数据的最新版本。
atomic是使用volatile和CAS来实现的

八、Java内存模型

(一) 什么是 Java 的内存模型, Java 中各个线程是怎么彼此看到对方的变量的?

Java 的内存模型定义了程序中各个变量的访问规则, 即在虚拟机中将变量存储到内存和从内存中取出这样的底层细节。
此处的变量包括实例字段、 静态字段和构成数组对象的元素, 但是不包括局部变量和方法参数, 因为这些是线程私有的, 不会被共享, 所以不存在竞争问题。
Java 中各个线程是怎么彼此看到对方的变量的呢? Java 中定义了主内存与工作内存的概念:
所有的变量都存储在主内存, 每条线程还有自己的工作内存, 保存了被该线程使用到的变量的主内存副本拷贝。
线程对变量的所有操作( 读取、 赋值) 都必须在工作内存中进行, 不能直接读写主内存的变量。 不同的线程之间也无法直接访问对方工作内存的变量, 线程间变量值的传递需要通过主内存。

(二)内存模型的三大特性:

原子性:Java内存模型保证了read、load、use、assign、store、write、lock、unlock操作具有原子性
实现:原子类、synchronized
可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性
实现:volatile、synchronize、final
有序性:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。
实现:volatile、synchronized
Happens-before 仅仅要求前一个操作结果,对后一个操作结果可见

(三) 请对比下 volatile 对比 Synchronized 的异同。

Synchronized 既能保证可见性, 又能保证原子性, 而 volatile 只能保证可见性, 无法保证原子性。
Thread Local 和 Synchonized 都用于解决多线程并发访问, 防止任务在共享资源上产生冲突。 但是 Thread Local 与 Synchronized 有本质的区别。
Synchronized 用于实现同步机制, 是利用锁的机制使变量或代码块在某一时该只能被一个线程访问, 是一种 “ 以时间换空间” 的方式。而 Thread Local 为每一个线程都提供了变量的副本, 使得每个线程在某一时间访问到的并不是同一个对象, 根除了对变量的共享, 是一种“ 以空间换时间” 的方式。

九、线程、多线程

线程:线程是进程中的一个执行单元,又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度。
多线程:它允许在进程中并发执行多个指令流,每个指令流都为称为一个线程,多线程机制下每个线程彼此之间相互独立,比较容易共享数据,通过并发执行的方式来提高程序的性能和效率。

十、线程池

(一)线程池的好处

线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。
使用线程池的好处
线程池改进了一个应用程序的响应时间。由于线程池中的线程已经准备好且等待被分配任务,应用程序可以直接拿来使用而不用新建一个线程。
1、降低资源消耗。重复利用已创建线程,降低线程创建与销毁的资源消耗。
2、提高响应效率。任务到达时,不需等待创建线程就能立即执行。
3、提高线程可管理性。
4、防止服务器过载。内存溢出、CPU耗尽

(二)Java 中的线程池是如何实现的?

•在 Java 中, 所谓的线程池中的“ 线程” , 其实是被抽象为了一个静态内部类 Worker, 它基于 AQS 实现, 存放在线程池的Hash Set< Worker> workers 成员变量中;
•而需要执行的任务则存放在成员变量 work Queue Blocking Queue< Runnable> work Queue) 中。
这样, 整个线程池实现的基本思想就是: 从 work Queue 中不断取出需要执行的任务, 放在 Workers 中进行处理。
生产者-消费者模型
生产者(调用submit或execute方法)将任务放入队列
消费者循环从队列中取出任务处理任务

(三)创建线程池的几个核心构造参数?

Java 中的线程池的创建其实非常灵活, 我们可以通过配置不同的参数, 创建出行为不同的线程池, 这几个参数包括:
core Pool Size: 线程池的核心线程数。
maximum Pool Size: 线程池允许的最大线程数。
keep Alive Time: 超过核心线程数时闲置线程的存活时间。
work Queue: 任务执行前保存任务的队列, 保存由 execute 方法提交的 Runnable 任务。

(四)线程池中的线程是怎么创建的? 是一开始就随着线程池的启动创建好的吗?

显然不是的。 线程池默认初始化后不启动 Worker, 等待有请求时才启动。
每当我们调用 execute() 方法添加一个任务时, 线程池会做如下判断:
•如果正在运行的线程数量小于 core Pool Size, 那么马上创建线程运行这个任务;
•如果正在运行的线程数量大于或等于 core Pool Size, 那么将这个任务放入队列;
•如果这时候队列满了, 而且正在运行的线程数量小于
maximum Pool Size, 那么还是要创建非核心线程立刻运行这个任务;
•如果队列满了, 而且正在运行的线程数量大于或等于
maximum Pool Size, 那么线程池会抛出异常Reject Execution Exception。
当一个线程完成任务时, 它会从队列中取下一个任务来执行。 当一个线程无事可做, 超过一定的时间( keep Alive Time) 时, 线程池会判断。
如果当前运行的线程数大于 core Pool Size, 那么这个线程就被停掉。所以线程池的所有任务完成后, 它最终会收缩到 core Pool Size 的大小。

(五)既然提到可以通过配置不同参数创建出不同的线程池, 那么Java 中默认实现好的线程池又有哪些呢? 请比较它们的异同。

1.newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2.newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
3.newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,
会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
4.newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

(六)如何实现线程安全

同步代码块 同步方法 Lock锁机制, 通过创建Lock对象,采用lock()加锁,unlock()解锁,来保护指定的代码块

十一、wait、sleep、yield

(一) 如何唤醒sleep的线程

wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。

(二) sleep和wait的区别

sleep() 使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有Synchronized同步块,其他线程仍然不同访问共享数据。注意该方法要捕获异常。比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
wait()方法使当前线程暂停执行并释放对象锁标志,让其他线程可以进入Synchronized数据块,当前线程被放入对象等待池中。当调用 notify()方法后,将从对象的等待池中移走一个任意的线程,并放到锁标志等待池中,只有锁标志等待池中的线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。

(三)sleep和yield的区别

yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。sleep()可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;yield()只能使同优先级的线程有执行的机会。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值