0%

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决一个全局使用的类频繁地创建与销毁。

饿汉模式

1
2
3
4
5
6
7
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton newInstance(){
return instance;
}
}

类的构造函数定义为private,保证其他类不能实例化此类,然后提供了一个静态实例并返回给调用者。

饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。

它的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。

它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。

这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。但是,如果单例占用的内存比较大,或单例只是在某个特定场景下才会用到,使用饿汉模式就不合适了,这时候就需要用到懒汉模式进行延迟加载。

懒汉模式

1
2
3
4
5
6
7
8
9
10
public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton newInstance(){
if(null == instance){
instance = new Singleton();
}
return instance;
}
}

懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。

如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。

但是这里的懒汉模式并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁解决线程同步问题,但加锁会影响效率。

双重校验锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

可以看到在同步代码块外多了一层instance为空的判断。由于单例对象只需要创建一次,如果后面再次调用getInstance()只需要直接返回单例对象。因此,大部分情况下,调用 getInstance() 都不会执行到同步代码块,从而提高了程序性能。

不过还需要考虑一种情况,假如两个线程A、B,A执行了if (instance == null)语句,它会认为单例对象没有创建,此时线程切到B也执行了同样的语句,B也认为单例对象没有创建,然后两个线程依次执行同步代码块,并分别创建了一个单例对象。为了解决这个问题,还需要在同步代码块中增加if (instance == null)语句。

这里要提到Java中的指令重排优化。所谓指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。

这个问题的关键就在于由于指令重排优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化,若紧接着另外一个线程来调用 getInstance,取到的就是状态不正确的对象,程序就会出错。

在JDK1.5及之后版本增加了volatile关键字volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免改该问题。

登记式/静态内部类

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {  
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}

private Singleton (){}
}

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

这种方式是 Singleton 类被装载了,instance 不一定被初始化,利用了类加载机制来保证只创建一个instance实例。它与饿汉模式一样,也是利用了类加载机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。

枚举

1
2
3
4
public enum Singleton{
instance;
public void whateverMethod(){}
}

而枚举类很好的解决了两个问题:不需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例;其次可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。

使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。

枚举方式虽然很完美的解决了各种问题,但是这种写法多少让人感觉有些生疏。

volatile

当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

  1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2. 禁止进行指令重排序。
  3. 不保证原子性

线程池

  1. 当我们调用 shutdown 等方法关闭线程池后,如果再向线程池内提交任务,就会遭到拒绝。
  2. 线程池没有空闲线程(线程池的线程达到了最大线程数,并且都在执行任务)并且队列已经满了,不能在存放任务了。

拒绝策略

AbortPolicy

这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。

DiscardPolicy

当有新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

DiscardOldestPolicy

丢弃任务队列中的头结点,通常是存活时间最长的任务,它也存在一定的数据丢失风险。

CallerRunsPolicy

第四种拒绝策略是 ,相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这样做主要有两点好处。

第一点新提交的任务不会被丢弃,这样也就不会造成业务损失。 第二点好处是,由于谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当于是一个负反馈。在此期间,线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期。

Hash Map/Hash Table

构造器,构造代码块,静态代码块的执行顺序

string,stringbuilder和stringbuffer的区别

NIO/IO

定义

  • 一个分布式运算程序的编程框架,是用户开发“基于 Hadoop 的数据分析应用”的核心框架。

  • 核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个 Hadoop 集群上。

Read more »

java.lang

Object 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}

if (nanos > 0) {
timeout++;
}

wait(timeout);
}
Read more »

OSI模型

物理层

  • 主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。
  • 主要作用是传输比特流。
  • 这一层的数据叫做比特。

数据链路层

  • 主要将从物理层接收的数据进行 MAC 地址的封装与解封装。
  • 这一层的数据叫做帧。
  • 在这一层工作的设备是交换机,数据通过交换机来传输。

网络层

  • 主要将从下层接收到的数据进行 IP 地址的封装与解封装。
  • 这一层的数据叫做报文。
  • 这一层工作的设备是路由器。

传输层

  • 定义了一些传输数据的协议和端口号。
  • 主要是将从下层接收的数据进行分段传输,到达目的地址后再进行重组。
  • 常常把这一层数据叫做段。

会话层

  • 主要在系统之间发起会话或或者接受会话请求。

表示层

  • 主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等。

应用层

  • 主要是一些终端的应用。

TCP/IP 模型

  • 因特网整个 TCP/IP 协议族。
  • 从协议分层模型方面来讲,TCP/IP 由四个层次组成:网络访问层、网络层、传输层、应用层。

网络访问层(Network Access Layer)

  • 在 TCP/IP 参考模型中并没有详细描述,只是指出主机必须使用某种协议与网络相连。

网络层(Internet Layer)

  • 整个体系结构的关键部分,其功能是使主机可以把分组发往任何网络,并使分组独立地传向目标。
  • 这些分组可能经由不同的网络,到达的顺序和发送的顺序也可能不同,高层需要自行对分组进行排序。
  • 使用因特网协议(IP,Internet Protocol)。

传输层(Tramsport Layer)

  • 使源端和目的端机器上的对等实体可以进行会话。

  • 在这一层定义了两个端到端的协议:

    • 传输控制协议(TCP,Transmission Control Protocol):TCP 是面向连接的协议,它提供可靠的报文传输和对上层应用的连接服务。为此,除了基本的数据传输外,它还有可靠性保证、流量控制、多路复用、优先权和安全性控制等功能。
    • 用户数据报协议(UDP,User Datagram Protocol):UDP 是面向无连接的不可靠传输的协议,主要用于不要 TCP 的排序和流量控制等功能的应用程序。

应用层(Application Layer)

  • 包含所有的高层协议,包括:
    • 虚拟终端协议(TELNET, TELecommunications NETwork)
    • 文件传输协议(FTP,File Transfer Protocol)
    • 电子邮件传输协议(SMTP,Simple Mail Transfer Protocol)
    • 域名服务(DNS,Domain Name Service)
    • 超文本传送协议 (HTTP,HyperText Transfer Protocol)

数据包

  • 源端口号(16 位):(连同源主机 IP 地址)标识源主机的一个应用进程。
  • 目的端口号(16 位):(连同目的主机 IP 地址)标识目的主机的一个应用进程,这两个值加上 IP 报头中的源主机 IP 地址和目的主机 IP 地址唯一确定一个 TCP 连接。
  • 顺序号(seq,32 位):用来标识从 TCP 源端向 TCP 目的端发送的数据字节流,表示在这个报文段中的第一个数据字节的顺序号。
  • 确认号 (ack,32 位):包含发送确认的一端所期望收到的下一个顺序号,是上次已成功收到数据字节顺序号加 1 ,且只有 ACK 标志为 1 时确认序号字段才有效。
  • TCP 报头长度(4 位):给出报头中 32bit 字的数目,指明数据从哪里开始,需要这个值是因为任选字段的长度是可变的。
  • 保留位(6 位):保留给将来使用,目前必须置为 0 。
  • 控制位(control flags ,6 位):有 6 个标志比特,它们中的多个可同时被设置为 1:
    • URG :为 1 表示紧急指针有效,为 0 则忽略紧急指针值。
    • ACK :为 1 表示确认号有效,为 0 表示报文中不包含确认信息,忽略确认号字段。
    • PSH :为 1 表示是带有 PUSH 标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满。
    • RST :用于复位由于主机崩溃或其他原因而出现错误的连接。它还可以用于拒绝非法的报文段和拒绝连接请求。一般情况下,如果收到一个 RST 为 1 的报文,那么一定发生了某些问题。
    • SYN :同步序号,为 1 表示连接请求,用于建立连接和使顺序号同步(synchronize )。
    • FIN :用于释放连接,为 1 表示发送方已经没有数据发送了,即关闭本方数据流。
  • 窗口大小(16 位):数据字节数,表示从确认号开始,本报文的源方可以接收的字节数,即源方接收窗口大小,最大为 65535 字节。
  • 校验和(16 位):对整个的 TCP 报文段(包括 TCP 头部和 TCP 数据)以 16 位字进行计算所得,是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证。
  • 紧急指针(16位):只有当URG标志置1时紧急指针才有效,是发送端向另一端发送紧急数据的一种方式。

三次握手

100327002629

  1. 发起建立连接请求:主机A发送 SYN=1,seq=x
  2. 确认建立连接请求:主机B发送 SYN=1,ACK=1,seq=y,ack=x+1
  3. 建立连接:主机A发送ACK=1,seq=y+1,ack=x+1

四次挥手

因为 TCP 连接是全双工的,所以进行关闭时每个方向上都要单独进行关闭。

当一方完成它的数据发送任务,就发送一个 FIN 来向另一方通告将要终止这个方向的连接。

100327022731

  1. 主机A关闭连接请求:主机A发送 FIN=1。FIN_WAIT_1
  2. 主机B确认关闭连接请求:主机B发送 ACK=1。CLOSE_WAIT 半关闭
  3. 主机B关闭连接请求:主机B发送 FIN=1。LAST_ACK
  4. 主机A确认关闭连接请求:主机A发送 ACK=1。CLOSED

HTTP

  • 无状态的协议:客户端和服务端之间不需要建立持久的连接,服务器端不保留连接的有关信息。
  • 遵循请求(Request)/应答(Response)模型:客户端向服务端发送请求,服务端处理请求并返回适当的应答。

传输流程

  1. 地址解析

    • 解析地址中的协议名、主机名、端口、对象路径等信息。
    • 其中需要 DNS 解析域名得到主机的 IP 地址。
  2. 封装 HTTP 请求数据包

    • 以上数据结合本纪信息,封装数据包。
  3. 封装 TCP 包并建立连接

  4. 客户端发送请求命令

    • 建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL)、协议版本号,后边是 MIME 信息(包括请求修饰符、客户端信息和可能的内容)。
  5. 服务器响应

    • 服务器接到请求后,给予相应的响应信息。
    • 格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是 MIME 信息(包括服务器信息、实体信息和可能的内容)。
  6. 服务器关闭 TCP 连接

    • 一般情况下,一旦 Web 服务器向浏览器发送了请求数据,就要关闭 TCP 连接。
    • 如果其头信息加入 Connection:keep-alive,TCP 连接在发送后将仍然保持打开状态,客户端可以继续通过相同的连接发送请求。
    • 保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

HTTPS

  • 全称:Hypertext Transfer Protocol over Secure Socket Layer。
  • 是以安全为目标的 HTTP 通道,即HTTP 下加入 SSL 层,其安全基础是 SSL,所用端口号为 443。

获取过程

  1. 建立连接获取证书

    • SSL 客户端通过 TCP 和服务器建立连接,在连接的协商(握手)过程中请求证书。
    • 客户端发出一个消息(包括可实现的算法列表和其它一些需要的消息)给服务端,服务器端会回应一个数据包(包括这次通信所需要的算法),然后服务端向客户端返回证书(包括服务端域名、申请证书的公司、公共秘钥)。
  2. 证书验证

    • 客户端在收到证书后,会判断签发这个证书的公共签发机构,并使用这个机构的公共秘钥确认签名是否有效。
    • 客户端还会确保证书中列出的域名就是它正在连接的域名。
  3. 数据加密和传输

    • 如果确认证书有效,那么生成对称秘钥并使用服务器的公共秘钥进行加密。
    • 然后发送给服务器,服务器使用它的私钥对它进行解密,这样两台计算机可以开始进行对称加密进行通信。

CDN(Content Delivery Network)

  • 内容分发网络,一般包括分发服务系统、负载均衡系统和管理系统。

分发服务系统

  • 基本的工作单元是各个 Cache 服务器,负责直接响应用户请求,将内容快速分发到用户,同时还负责内容更新,保证和源站内容的同步。
  • 根据内容类型和服务种类的不同,分发服务系统分为多个子服务系统,如:网页加速服务、流媒体加速服务、应用加速服务等,每个子服务系统都是一个分布式的服务集群,由功能类似、地域接近的分布部署的 Cache 集群组成。
  • 向上层的管理调度系统反馈各个 Cache 设备的健康状况、响应情况、内容缓存状况等,以便管理调度系统能够根据设定的策略决定由 哪个 Cache 设备来响应用户的请求。

负载均衡系统

  • 整个 CDN 系统的中枢,负责对所有的用户请求进行调度,确定提供给用户的最终访问地址。
  • 使用分级实现,最基本的两极调度体系包括全局负载均衡(GSLB)和本地负载均衡(SLB)。
    • GSLB 根据用户地址和用户请求的内容,主要根据就近性原则,确定向用户服务的节点。一般通过 DNS 解析或者应用层重定向(Http 3XX 重定向)的方式实现。
    • SLB 主要负责节点内部的负载均衡,当用户请求从 GSLB 调度到 SLB 时,SLB 会根据节点内各个 Cache 设备的工作状况和内容分布情况等对用户请求重定向。SLB 的实现有四层调度(LVS)、七层调 度(Nginx)和链路负载调度等。

管理系统

  • 分为运营管理和网络管理子系统。
  • 实现对 CDN 系统的设备管理、拓扑管理、链路监控和故障管理,为管理员提供对全网资源的可视化的集中管理。
  • 运营管理是对 CDN 系统的业务管理,负责处理业务层面的与外界系统交互所必须的一些收集、整理、 交付工作。包括用户管理、产品管理、计费管理、统计分析等。

POST 和 GET 区别

POST和GET是HTTP请求的两种方式,都可实现将数据从浏览器向服务器发送带参数的请求。

HTTP请求底层协议都是TCP/IP,所以两者没有本质的区别。

  • GET - 从指定的资源请求数据。
  • POST - 向指定的资源提交要被处理的数据
  1. GET 提交的数据放在URL中,POST 则不会。这是最显而易见的差别。这点意味着 GET 更不安全( POST 也不安全,因为HTTP是明文传输抓包就能获取数据内容,要想安全还得加密)
  2. GET 回退浏览器无害,POST 会再次提交请求(GET 方法回退后浏览器再缓存中拿结果,POST 每次都会创建新资源)
  3. GET 提交的数据大小有限制(是因为浏览器对 URL 的长度有限制,GET 本身没有限制),POST 没有
  4. GET 可以被保存为书签,POST 不可以。
  5. GET 能被缓存,POST 不能
  6. GET 只允许 ASCII 字符,POST 没有限制
  7. GET 会保存再浏览器历史记录中,POST 不会