线程池
线程池中有很多线程,如何让这些线程保持不死呢?
线程池维护着一个队列(working queue),要让线程池执行我们的任务代码,怎么样表示一个线程执行的任务呢?Runnable对象。这个队列里放的是一个个的Runnable对象。
ExecutorService newCachedThreadPool()
- 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们
- 对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能(短期就是执行时间很短,异步任务就是你可能想让他在子线程中执行)
- 如果现有线程没有可用的,则创建一个新线程并添加到池
- 终止并从缓存中移除那些已有 60 秒钟未被使用的线程
对于线程池而言可以向他提交两种类型的异步任务:
1。Runnable 子类对象没有返回值(Runnable运行在子线程中的方法是run()方法,run()方法没有返回值)
2. 在Callable<>的<>内指定返回值类型。类比Runnable,其call()方法的使用类似于Runnable的run()方法。
并用Future对象接收Callable任务提交给线程池后运行的结果。
Future result = executorService.submit(callable);
ExecutorService newFixedThreadPool(int nThreads)
- 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程
- 以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。
- 如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待
固定线程数,当有新任务需要执行时,如果线程池中的线程都有任务需要执行就将新任务放到暂存队列中,有线程空闲后再来执行新任务。
ExecutorService newSingleThreadExecutor()
- 创建一个使用单个 worker 线程的 Executor,
- 以无界队列方式来运行该线程
public class Demo01Threadpool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//newCachedThreadPool()
ExecutorService executorService = Executors.newCachedThreadPool();
//使用线程池,通常是向线程池提交异步任务 也就是Ruanable子类对象
MyTask myTask = new MyTask(); //一个异步任务
executorService.submit(myTask);
//Callable接口
Callable callable = new MyCallable();
//Future 表示异步计算的结果
Future<Integer> result = executorService.submit(callable);
//获取异步任务执行的结果
Integer integer = result.get();
System.out.println(integer);
//其他线程池,我们对他的使用 一模一样的方式
//ExecutorService executorService1 = Executors.newFixedThreadPool(10);
//ExecutorService executorService2 = Executors.newSingleThreadExecutor();
// 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
//executorService.shutdown();
//试图停止所有正在执行的活动任务,暂停处理正在等待的任务(用Thread.interrupt),
// 并返回等待执行的任务列表(告诉你哪些任务没执行)。
executorService.shutdownNow();
}
}
class MyTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "hello thread pool");
}
}
//在Callable<>的<>内指定返回值类型
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
TimeUnit.SECONDS.sleep(3);
int a = 10;
int b = 20;
return a + b;
}
}
线程池原理:
··············································································································································································································
定时任务
Timer: 一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
用来安排任务执行的时间, 但具体在指定的时间执行的任务是哪个呢? TimerTask对象
-
schedule(TimerTask task, Date time)
安排在指定的时间执行指定的任务。 -
void schedule(TimerTask task, Date firstTime, long period) period 毫秒
安排指定的任务在指定的时间开始进行重复的固定延迟执行。 -
void schedule(TimerTask task, long delay)
安排在指定延迟后执行指定的任务。 -
void schedule(TimerTask task, long delay, long period)
安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。 -
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
安排指定的任务在指定的时间开始进行重复的固定速率执行。
cancel 方法而言:
- Timer的cancel方法,当前Timer中所有的定时任务都会被取消
- TimerTask的cancel方法,只终止这个个TimerTask对象的执行,前提是(他还没有被Timer调度执行)
public class Demo02Timertask {
public static void main(String[] args) {
test();
//一个定时前,安排定时任务执行的时机
Timer timer = new Timer();
//代表一个定时任务
MyTimmerTask myTimmerTask = new MyTimmerTask();
//timer.schedule(myTimmerTask, 3000, 1000);
timer.schedule(myTimmerTask, 3000, 1000);
//终止此计时器,丢弃所有当前已安排的任务。
//timer.cancel();
}
public static void test() {
System.out.println("test");
}
}
//定义一个定时任务
class MyTimmerTask extends TimerTask {
@Override
public void run() {
cancel();
System.out.println("我是定时任务");
}
}
Timer里维护了一个小顶堆,以距当前时间执行的时间间隔从小到大的顺序排列。Timer中有一个线程会依次从堆顶取出TimerTask定时任务,依次执行,这个线程会一直对这个小顶堆轮询。那我们怎么让其结束轮询呢?Timer.cancel();
··············································································································································································································
计算机网络
是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来 (物理互联)
在网络操作系统,网络管理软件及网络通信协议的管理和协调下(管理)
实现资源共享和信息传递的计算机系统。
网络编程
就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换。
··············································································································································································································
OSI七层模型
-
物理层:关注在一条通信信道上传输原始比特(01序列)
其功能是确保当一方发送了比特1,另一方收到的也是比特1,而不是0。
这里面涉及到的典型问题包括:
a. 用什么样的电信号表示0和1
b. 一个bit持续多少纳秒
c. 传输是否可以在两个方向同时进行
d. 初始连接如何建立,传输结束之后如何如何撤销连接
这些问题主要涉及,机械,电子,和时序接口等。以及物理层之下的物理传输介质等 -
数据链路层:将一个原始的传输设施转变成一条没有漏剪检传输错误的传输线路
为了完成这个任务,数据链路层具备如下功能:
a. 成帧
b. 差错控制(校验帧,确认 + 超时重传)
c. 流量控制
d. 广播式网络的数据链路层,还有介质访问控制问题 -
网络层:其中的一个核心问题是如何将数据包从源端路由到接收端
-
传输层:向上层提供端到端(计算机之间的通信归根到底是两个应用程序之间的通信,即进程之间的通信)的通信服务,屏蔽底层的通信细节
端到端通信:指的是应用程序对应的进程之间的通信
端口:整数值,一台主机中代表一个唯一的进程标志。
根据IP地址唯一确定一台主机,根据端口号唯一确定一个进程。
a. 提供应用进程之间的逻辑通信
b. 复用和分用
c. 向上层提供不同类型的通信服务
TCP:面向连接的可靠的服务(保证报文数据的有序到达)
UDP:无连接的不可靠的服务(不保证报文数据的顺序,广播报文数据) -
会话层:(记录谁发了消息,记录检查点)
会话层允许不同主机上的各个进程之间进行会话,会话层利用传输层提供的端到端的服务,向表示层提供它的增值服务(进程上的一次连接,发送信息的过程)
会话层负责管理主机间的会话,包括建立,管理及终止进程间的会话,会话层可以使用校验点,使通信会话在通信失效时,从校验点继续回复通信,实现数据同步。 -
表示层:表示层主要处理两个通信系统中交换信息的表示方式
不同机器采用的编码和表示方法不同,比如数据是否压缩,数据的加密和解密使用何种加密算法等等,这些都是由表示层决定的 -
应用层:
包含了用户通常所需要的各种协议。一个广泛使用的大家最熟悉的应用层协议,就是Http协议,它是www(万维网),还有其他一些应用层协议,比如和电子邮件相关的smtp,pop
,还有ftp协议等等。
IP地址
A:所谓IP地址就是给每个连接在Internet上的主机分配的一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节。例如一个采用二进制形式的IP地址是“00001010000000000000000000000001”,这么长的地址,人们处理起来也太费劲了。为了方便人们的使用,IP地址经常被写成十进制的形式,中间使用符号“.”分开不同的字节。于是,上面的IP地址可以表示为“10.0.0.1”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多。
B:IP地址的组成
IP地址 = 网络号码+主机地址
A类IP地址:第一段号码为网络号码,剩下的三段号码为本地计算机的号码
B类IP地址:前二段号码为网络号码,剩下的二段号码为本地计算机的号码
C类IP地址:前三段号码为网络号码,剩下的一段号码为本地计算机的号码
特殊地址:
127.0.0.1 回环地址,可用于测试本机的网络是否有问题. ping 127.0.0.1 代表当前主机的IP地址
DOS命令 ipconfig:查看本机IP地址
对于A、B、C不同类型的网络地址,都包含两个特设的IP地址。
xxx.xxx.xxx.0 网络地址 表示该C类网络本身
xxx.xxx.xxx.255 广播地址 代表的是这个网络中所有的主机
A类 1.0.0.1—127.255.255.254 (1)10.X.X.X是私有地址(私有地址就是在互联网上不使用,而被用在局域网络中的地址) (2)127.X.X.X是保留地址,用做循环测试用的。
B类 128.0.0.1—191.255.255.254 172.16.0.0—172.31.255.255是私有地址。169.254.X.X是保留地址。
C类 192.0.0.1—223.255.255.254 192.168.X.X是私有地址
保留地址:
D类 224.0.0.1—239.255.255.254 (组播的目的地址)
E类 240.0.0.1—247.255.255.254 (保留地址)
·········································································································································································································
其实我们实际生活中使用的是TCP/IP参考模型。在OSI参考模型发明之前,TCP/IP参考模型已经在市面上流行开了,已经有很多基于TCP/IP参考模型的软件在使用。
网络编程三要素:IP地址,端口号,以及传输层协议
端口号(只具有本地意义)
每个进程都有一个端口号
端口号标识进程的逻辑地址
取值范围0~65535(0到1023已经分配给固定的应用程序,我们只能用其他端口)
网络通信说到底是进程与进程间的通信,因此IP+端口号才能构成完整的目标地址
TCP建立连接与释放连接耗费一定的资源,所以,如果传输很少的数据,用UDP传输,因为UDP不需要建立连接,如果传输很多的数据,用TCP传输。
传输层的协议:TCP和UDP
TCP
发送数据前要首先建立连接(3次握手机制)
连接建立成功后,会建立数据传输的“通道”
在该通道下进行数据传输 (一对一的通信)
UDP (1对多)
发送数据时无需建立连接
每个发送的数据报大小限制在64KB
Socket套接字 = IP + 端口号
TCP三次握手协议:
第一次握手:建立连接时,客户端向服务器发送一个SYN包(握手信号),并计入SYN_SENT状态,等待服务器确认。
第二层握手:服务器收到SYN包,必须确认客户端的SYN,同时自己也发送一个SYN包,即SYN+ACK包(接收到消息并响应),此 时服务器进入SYN_SENT状态。
第三次握手:客户端收到服务器的SYN+ACK包,并向服务器发送确认包ACK,此包发送完毕,客户端和服务器进入 Established(已建立的,确认的)状态。即TCP连接成功,完成三次握手。
假如源主机第一握手,然后目的主机第二次握手,第二次握手后目的主机会准备建立连接的相应资源。如果源主机不停的发送连接请求,会把目的主机的资源耗费完,这就是泛洪攻击。
二:三次握手的核心思想
1:TCP作为一种可靠传输控制协议,其核心思想是:既要保证数据可靠传输,又要提高传输效率,而用三次恰恰可以满足以上两个方面的要求。
2:三次是保证双方互相明确对方能收,能发的最低值。理论上讲不论握手多少次都不能确认一条消息是“可靠”的,但通过三次握手后,至少可以确认tcp/ip协议是“可用”的。在此基础上,继续提高握手次数,不过是提高“它是可用的”这个结论的可信程度。
3:可以将三次握手过程形象的归纳如下:
1:A发,B收,B知道A能发;
2:B发,A收,A知道B能发能收;
3:A发,B收,B知道A能收。
4:“三次握手”的本质是:信道是不可靠的,但是通信双方需要就某个协议达成一致,而要解决这个问题,无论你在消息中包含什么信息,三次通信是理论上的最小值。因为三次握手不是TCP本身的要求,而是为了满足“在不可靠的信道上可靠的传输信息”这一需要导致的。
思考:为什么要三次握手呢,有人说两次握手就好了
举例:已失效的连接请求报文段。
client发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server,本来这已经是一个失效的报文,但是server端接收到这个请求报文后,还是会想client发出确认的报文,表示同意连接。假如不采用三次握手,那么只要server发出确认,新的建立就连接了,但其实这个请求是失效的请求,client是不会理睬server的确认信息,也不会向服务端发送确认的请求,但是server认为新的连接已经建立起来了,并一直等待client发来数据,这样,server的很多资源就没白白浪费掉了,采用三次握手就是为了防止这种情况的发生,server会因为收不到确认的报文,就知道client并没有建立连接。这就是三次握手的作用。
··········································································································································································································
TCP四次挥手:
第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当 然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但此时主动关闭方还可以接受数据。
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
··········································································································································································································
为什么建立连接是三次握手,释放连接是四次握手?
因为当服务端收到客户端的SYN请求报文后,可以直接发送SYN+ACK报文,其中ACK报文是用来应答的,SYN报文是用来同步的。
但是关闭连接时,当服务端收到FIN报文时,很可能不会立即关闭socket,所以只能先回复一个ACK报文。告诉客户端,你发的FIN报文我已经收到了,只有等服务端所有的报文都发送完,才会发送FIN报文,因此不能一起发送。故需四次挥手。