复习笔记1-java基础

软件开发流程
业务提出需求、可行性分析、立项、设计(功能模块、架构设计)、开发、上线、验收、后续维护

JVM、JRE 、JDK关系

JVM :(Java Virtual Machine),就是 Java 虚拟机,它将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),所以说 jvm 是 Java 能够跨平台的核心,有JVM并不代表就可以执行class了,JVM执行.class还需要JRE下的lib类库的支持,尤其是rt.jar

JRE :(Java Runtime Environment)Java 运行时环境。它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。

JDK :(Java Development Kit),Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。它能够创建和编译程序。

显然,这三者的关系是:一层层的嵌套关系。JDK>JRE>JVM。

jre 不能用于创建新程序,jdk 可以,linux服务器上一般只用来运行java程序,但为啥不只装jre而要装jdk?因为项目可能用到 JSP ,需要转换为 Java servlet,所以就需要安装jdk了

为什么我们的电脑在装完 jdk 后会有两个版本的 jre?
jdk目录下的jre和并排目录的jre目录之间没有联系。它俩是一样的,可能就是刚开始大家都不清楚 jdk 和 jre 之间的关系,默认都安装上了

复习笔记1-java基础_第1张图片

Oracle JDK 和 OpenJDK 的对比

1.OpenJDK 是一个参考模型并且是完全开源的,而 Oracle JDK 是 OpenJDK 的一个实现,并不是完全开源的;
2.Oracle JDK 比 OpenJDK 更稳定,代码几乎相同,但 Oracle JDK 有更多的类和一些错误修复

final,static,this,super 关键字

final: 类、变量、方法。修饰变量不能更改,修饰类,不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
static:修饰成员变量和成员方法,被类中所有对象共享,静态代码块,执行顺序(静态代码块—>非静态代码块—>构造方法),静态代码块只执行一次,非静态代码块和构造方法一样可执行多次
this: 用于引用类的当前实例
super:用于从子类访问父类的变量和方法

字符型常量和字符串常量的区别

1.形式上: 字符常量是单引号引起的一个字符 char temp = 'a' ; 字符串常量是双引号若干个字符 String temp = "aaaaaa"
2.含义上: 字符常量相当于一个整型值( ASCII 值  a 为 97),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
3.占内存大小:字符常量只占 2 个字节(char 在 Java 中占两个字节), 字符串常量占若干个字节 

静态变量和静态常量

public static String VALUE = "static value loading";   # 静态变量     
public static final String FIANL_VALUE = "fianl value loading"; # 静态常量

基本数据类型

基本数据类型(全部小写) 取值范围 所占字节长度
boolean true/false 理论上占用1bit,1/8字节,实际处理按1byte处理
byte -128~127 1字节
short -32768~32757 2字节
int -2的31次方 至 2的31次方-1 4字节
long -2的63~2的63-1 8字节
char 采用Unicode编码,表示范围\u0000~\uFFFF 2字节
float 4字节
double 8字节

float单精度,double双精度

BigInteger(大整型): long 的范围也有限制(大概19位),所以出现了BigInteger(大整型)
BigDecimal (大浮点型): 用于解决 float 和 double 类型对数字计算的精度丢失问题

八种包装类(全部大写):

Boolean,Byte,Short,Integer,Long,Character,Float,Double
Java 包装类的常量池。Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True Or False。
String 及 Integer 等其他包装类 有常量池

native
说明不是java写的,而是调用其他语言,比如 object 类的 clone()

面向对象和面向过程的区别

面向过程强调的是过程,性能更高,面向对象强调的是对象,有封装、继承、多态性的特性,易维护、易复用、易扩展,低耦合
封装:就是隐藏对象的属性和实现细节,仅对外提供公共访问方式。
继承:从已有类派生出新的类,新类吸收已有类的属性和方法,并能扩展新的属性,子类不能选择性的继承父类的属性方法
多态:一个对象具有多种的状态,具体表现为父类的引用指向子类的实例

面向对象的五大基本原则

1.单一职责原则:一个类只做一件事,低耦合高内聚思想的延伸
2.开放封闭原则:面向扩展开放,面向修改封闭
3.里氏替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能
4.依赖倒置原则:高层次的模块不应该依赖低层次的模块,他们都应该依赖于抽象,抽象不应该依赖于具体实现,具体实现应该依赖于抽象
5.接口分离原则:不应强迫客户端实现一个用不上的接口,或是说客户端不应该被迫依赖它们不使用的方法,使用多个专门的接口比使用单个接口要好

自动装箱与拆箱

装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;

Integer i = 10;  //装箱
int n = i;   //拆箱

抽象类(abstract)和接口(interface )的区别

区别:
1.抽象类可以有构造方法,可以有普通成员变量和非抽象的普通方法
2.接口中不能有构造方法,没有普通成员变量(jdk1.8 接口可以定义静态方法和默认方法default),变量只能是 public static final类型
3. 一个类可以实现多个接口,但只能继承一个抽象类。(jdk1.8 可以多继承了)

应用场景:
接口主要用于实现多继承的场景以及模块与模块之间的调用情况,抽象类主要用于当做基础类使用,即基类(基类里面的一些方法都有默认的方法实现,即实现接口的公用的代码,个性化的方法由各个子类去实现)

== 和 equals 的区别

对于基本数据类型来说,==比较的是值,且基本类型没有 equals 方法,对于引用数据类型来说,==比较的是对象的内存地址
equals() 方法存在两种使用情况:
类没覆盖 equals 方法 :通过 equals 比较两个对象时,是 调用Object类 equals() 方法也就是 ==方法,比较的是地址。
类覆盖了 equals 方法 :一般我们都覆盖 equals() 方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

在 Java 中定义一个不做事且没有参数的构造方法的作用

站在父类角度,如果我这个父类只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误

continue、break、和 return 的区别是什么

continue :指跳出当前的这一次循环,继续下一次循环。
break :指跳出整个循环体,继续执行循环下面的语句。
return 跳出整个方法,return 一般有两种用法:

return; :直接使用 return 结束方法执行,用于【没有返回值】函数的方法
return value; :return 一个特定值,用于【有返回值】函数的方法

跳出多层循环
// 在最外层循环语句前定义一个标号,注意ko后面是冒号:  
ko:for (int i = 0; i < 10; i++) {  
	for (int j = 0; j <= 10; j++) {  
		System.out.println("i=" + i + ",j=" + j);  
		if (j == 5) {
			break ko; //循环体内任意位置都可以使用带有标号的break语句跳出外层循环,结束整个循环。 
		} 
	}  
}  

静态属性和静态方法是否可以被继承,是否可以被重写

【静态属性和静态方法是可以被子类继承】,静态方法属于类的,在编译阶段已经静态绑定到类上,表现出来就是通过类名.方法名进行访问;所以【静态方法无法被子类重写】。

为什么重写 equals 时必须重写 hashCode 方法

hashCode() 是的 Object 的方法,是将对象的 内存地址 转换未 int 整数返回(哈希码)。该码的作用是确定该对象在哈希表中的索引位置

java 编程有约定:如果两个对象根据 equals 方法比较是相等的,那么调用这两个对象的任意一个 hashcode 方法都必须产生相同的结果。
通常实际业务场景两个Student new 的对象比较,一般是只要所有属性一致就当做相等,如果不重写 hashCode ,那么hashCode 肯定不会相等的

注:两个对象有相同的 hashcode 值,它们也不一定是相等的,因为 hashCode() 所使用的杂凑算法导致的

需要重写 hashCode 方法,主要是针对 Map、Set 等集合类型的使用;
一般如果使用 java 中的 Map 对象进行存储时,他会自动调用 hashCode 方法来比较两个对象是否相等

 Key k1 = new Key(1);
 Key k2 = new Key(1);
 HashMap hm = new HashMap();
 hm.put(k1, "Key with id is 1");    
 System.out.println(hm.get(k2));  # 如果没重写 hasCode(),获得是null,预想应该是有值的

a: Map、Set等集合类型存放的对象必须是唯一的;
b: 集合类判断两个对象是否相等,是先判断equals是否相等,如果 equals 返回 TRUE,还要再判断 HashCode 返回值是否 ture ,只有两者都返回 ture ,才认为该两个对象是相等的。

2、由于Object的 hashCode 返回的是对象的 hash 值,所以即使 equals 返回 TRUE,集合也可能判定两个对象不等,所以必须重写 hashCode方法,以保证当equals返回TRUE时,hashCode也返回Ture,这样才能使得集合中存放的对象唯一。
"SnailClimb".equals(str);// false 

JDK7后推荐使用 java.util.Objects#equals

Objects.equals(null,"SnailClimb");// false

源码如下

public static boolean equals(Object a, Object b) {
    // 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。
    return (a == b) || (a != null && a.equals(b));
}
每种原始类型都有默认值一样,如int默认值为 0,boolean 的默认值为 false,null 是任何【引用类型】的默认值,不严格的说是所有 Object 类型的默认值。
可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在 Java 中 null == null 将返回 true。
不能使用一个值为 null 的引用类型变量来调用非静态方法,否则会抛出异常

Object 类的常见方法

public final native Class<?> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。

hashCode() 、equals(Object obj)、clone() 、toString()

notify() //native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
notifyAll() //native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。

wait(long timeout)  //native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。
finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作

构造方法特点,是否可被 override(重写)

1.名字与类名相同。2.没有返回值,但不能用 void 声明构造函数。3.生成类的对象时自动执行,无需调用。
构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况

Sting的几种类型

String(不可变), StringBuffer(加锁安全安全) StringBuild (不安全效率高), 线程安全效率问题

IO:(通信连接)BIO(同步阻塞)、NIO( 同步非阻塞)、AIO(异步非阻塞的)

集合 Collection 分 list(有序可重复) 和 set(唯一), map(键值)

考察线程安全问题和增删查询效率问题及数据结构类型(数组有索引查询快,增删慢,链表相反)
ArrayList(数组)、LinkedList(链表)、Vector(数组,加锁安全)
HashSet无序(子类LinkedHashSet,按插入排序)和 TreeSet (红黑树自平衡二叉树、支持自然排序和自定义Comparator )
HashMap key不重复,key 和value 都允许为 null,要安全就用 concurrentHashMap ,Hashtable,老的加锁了线程安全,且不支持 key 或者 value 为 null
LinkedHashMap按照插入顺序排序,TreeMap 自然排序和自定义排序

HashMap 和 Hashtable 区别

HashMap	和 Hashtable 区别(线程安全、key是否null、扩容)
1.HashMap 线程不安全,Hashtable 线程安全
2.HashMap可以使用 null作为key,Hashtable 不允许,HashMap 以 null 作为 key 时,存储在 table 数组的第一个节点table[0]
3.HashMap 的初始容量为16,Hashtable 初始容量为11,两者的填充因子(扩容临界系数)默认都是 0.75。
 HashMap 扩容时是当前容量翻倍即:capacity*2,Hashtable 扩容时是容量翻倍+1即: capacity*2+1
HashMap	存储数据用的哈希表,非线程安全的,多线程环境下可以采用 concurrent 并发包下的 concurrentHashMap 。 JDK1.8 之前 HashMap 由数组+链表组成的,
数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。1.8 以后变为数组+链表+红黑树的存储方式,当【链表】长度大于 8
(将链表转换成红黑树前会判断,如果当前【数组】的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间

jdk1.7(数组+链表)

复习笔记1-java基础_第2张图片

jdk1.8(数组+链表+红黑树)

复习笔记1-java基础_第3张图片

ConcurrentHashMap 在 JDK1.7 和 JDK1.8 的实现方式

先来看下JDK1.7

JDK1.7 中的 ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成,即 ConcurrentHashMap 把哈希桶数组切分成小数组(Segment ),每个小数组有 n  HashEntry 组成。Segment 继承了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。Segment 默认为 16,也就是并发度为 16

再来看下JDK1.8

在数据结构上, JDK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的 Node数组+链表+红黑树结构;在锁的实现上,抛弃了原有的 Segment 分段锁,采用 CAS + synchronized 实现更加细粒度的锁。

将锁的级别控制在了更细粒度的哈希桶数组元素级别,也就是说只需要锁住这个链表头节点(红黑树的根节点),就不会影响其他的哈希桶数组元素的读写,大大提高了并发度。

JDK1.8 中用内置锁 synchronized 替换 可重入锁 ReentrantLock

 JDK1.6 中,对 synchronized 锁的实现引入了大量的优化,减少内存开销 。假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承 AQS 来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。
没有关系。哈希桶数组table用 volatile 修饰主要是保证在数组扩容的时候保证可见性。

ConcurrentHashMap 专门是给多个线程访问的。举个例子:

// 在线用户管理类
public class UserManager {
private Map<String, User> userMap = new ConcurrentHashMap<>();

// 当用户登入时调用
public void onUserSignIn(String sessionId, User user) {
this.userMap.put(sessionId, user);
}

// 当用户登出或超时时调用
public void onUserSignOut(String sessionId) {
this.userMap.remove(sessionId);
}

public getUser(String sessionId) {
return this.userMap.get(sessionId);
}
}
当有很多用户同时登入和登出时,onUserSignIn() 和 onUserSignOut() 就会有很多线程同时调用。

排序的实现①该类实现Comparable ②匿名内部类实现Comparator 如 Collections.sort(arrayList, new Comparator() {…}

数组和链表的区别(定义,优缺点)

不同:链表是链式的存储结构;数组是顺序的存储结构。
链表通过指针来连接元素与元素,单向,双向,循环,而数组则是把所有元素按次序依次存储。
链表的插入删除元素相对数组较为简单,不需要移动元素,且较为容易实现长度扩充,但是寻找某个元素较为困难;
数组寻找某个元素较为简单,但插入与删除比较复杂,由于最大长度需要再编程一开始时指定,故当达到最大长度时,扩充长度不如链表方便。
相同:两种结构均可实现数据的顺序存储,构造出来的模型呈线性结构。

Arraylist 与 LinkedList 区别

1.ArrayList 和 LinkedList 都不是线程安全
2.Arraylist 底层使用的是 Object 数组;LinkedList 是双向链表数据结构(有next也有previous)
3.对于增删操作,LinkedList更快,ArrayList 查询快 。

是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
内存空间占用: ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。

ArrayList扩容过程

List扩容实现步骤(jdk1.8)
以无参数构造方法创建 ArrayList 时,初始化是一个空数组。当真正对数组进行添加元素时,才真正分配容量,向数组中添加第一个元素时,
数组容量扩为 10,当容量满了后扩容,每次扩容1.5倍。
1、扩容:把原来的数组复制到另一个内存空间更大的数组中
2、添加元素:把新元素添加到扩容以后的数组中

Comparable 和 Comparator 区别

Comparable 可以看做是内部比较器,自定义的业务类去实现它,重写 compareTo 方法,

public class User implements Serializable, Comparable<User> {
    private int age;
    @Override
    public int compareTo(User o) {
        return this.age - o.age;
    }
}

Comparator可以看做是外部比较器,自己的业务类不用去实现,compare(User o1, User o2) 方法传入两个类去实现比较

public class MyComparator implements Comparator<User> {
    @Override
    public int compare(User o1, User o2) {
        return o1.getName().charAt(0)-o2.getName().charAt(0);
    }
}

Arrays.asList() 数组转集合存在的问题

List list = Arrays.asList(“a”,“b”,“c”);
(1)该方法适合【对象型】数组(String、Integer…),想用在基本数据类型可以用 spring 提供的CollectionUtils.arrayToList(a)
(2)该方法不建议使用于基本数据类型的数组(byte,short,int,long,float,double,boolean)
(3)存在转换前的数组 array 和转换后的 list 同步更新问题
(4)是定长的,不支持 add()、remove()、clear()等方法,
参考:https://blog.csdn.net/kzadmxz/article/details/80394351

SOA 面向服务

iaas(infrastructure as a Service),即基础设施即服务,其中包括处理CPU、内存、存储、网络和其它基本的计算资源等服务,该服务可以提高硬件的自动化管理,人与机器的解耦合,获得效率和提高资源的利用率

paas(Platform as a Service):即平台即服务,用来进行应用的自动化,应用与OS的解耦合,获得弹性/简化运维,该服务包括如数据库,web服务器,应用服务器

saas(Software as a Service):即软件服务,该服务属于IAAS和PAAS的结合,是有两种服务组合起来提供的一种服务
复习笔记1-java基础_第4张图片

heap 和 stack 区别:

java内存通常被划分为:堆(Heap)、栈(Stack)、方法区、程序计数器、本地方法(Native)栈

1.堆内存:先进先出,后进后出,存放 new 创建的对象和数组,它不会随方法的结束而消失。 方法中的局部变量使用 final 修饰后,也放在堆中,堆是被所有线程共享区域。几乎所有的对象实例都是在这分配内存,但该对象的引用是在栈(Stack)中分配

2.栈内存:后进先出,先进后出,程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用来存放方法或者局部变量等 当方法结束时,分配给这个方法的栈会释放,栈中变量也将被释放,执行String s = new String(“s”) 时,需要从两个地方分配内存:在堆中为 String 对象分配内存,在栈中为引用(这个堆对象的内存地址,即指针)分配内存。

3.程序计数器:可以看作一个指针。它指着当前程序(字节码)正在运行的那一行代码,它是 Java 运行时数据区中最小的一块,是线程私有的内存区域

4.本地方法栈(Native Stack):存储本地方法【Native】的调用状态,本地方法栈和 Java 虚拟机栈很像。Java 虚拟机栈是为了虚拟机执行字节码服务,本地方法栈则是为虚拟机执行 Native 方法服务的。它的作用就是支撑 Native 方法的执行、调用和退出。

5.方法区(Method Area): class 类的信息(包括类的版本,类名,字段,方法,接口等信息)和常量、静态变量及【常量池】在方法区中,该区域也被所有线程共享,(和堆一样)

-Xss --设置方法栈的最大值
-Xms -- 设置堆内存初始大小
-Xmx -- 设置堆内存最大值
-XX:MaxTenuringThreshold -- 设置对象在新生代中存活的次数
-XX:PretenureSizeThreshold -- 设置超过指定大小的大对象直接分配在旧生代中

垃圾回收

垃圾收集器管理的主要区域是堆内存,称为“GC 堆”(Garbage Collectioned Heap)。现在垃圾收集器基本都是采用的分代收集算法,可以细分为:新生代(Young Generation)和老年代(Old Generation),分代收集算法的思想:在分配对象遇到内存不足时,先对新生代进行GC(Young GC);当新生代GC之后仍无法满足内存空间分配需求时, 才会对整个堆空间以及方法区进行GC(Full GC)。
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。长期存活的对象也将进入老年代
复习笔记1-java基础_第5张图片

新生代(Young Generation)又分为:Eden 区 和 Survivor 区,Survivor区有分为From Space和To Space。Eden区是对象最初分配到的地方;默认情况下,From Space和To Space的区域大小相等。JVM进行Minor GC时,将Eden中还存活的对象拷贝到Survivor区中,还会将Survivor区中还存活的对象拷贝到Tenured区中。在这种GC模式下,JVM为了提升GC效率, 将Survivor区分为From Space和To Space,这样就可以将对象回收和对象晋升分离开来。新生代的大小设置有2个相关参数:

-Xmn -- 设置新生代内存大小。
-XX:SurvivorRatio -- 设置Eden与Survivor空间的大小比例

老年代(Old Generation): 大对象(指需要大量连续内存空间的 Java 对象,很长的字符串以及数组)和长期存活的对象(从Survivor 区熬过来)将进入老年代

永久代(Permanent Generation)即方法区,在Java 8中使用本地内存,存放的都是 jvm 初始化时加载器加载的一些类型信息(包括类信息、常量、静态变量等),这些信息的生存周期比较长,GC 不会在主程序运行期对 PermGen Space 进行清理,所以如果你的应用中有很多 CLASS 的话,就很可能出现 PermGen Space 错误。其相关设置参数:

-XX:PermSize --设置Perm区的初始大小
-XX:MaxPermSize --设置Perm区的最大值

GC 其实准确分类只有两大种
部分收集 (Partial GC):

新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。

整堆收集 (Full GC):收集整个 Java 堆和【方法区】。

垃圾判断算法:引用计数法、可达性分析算法

垃圾收集算法
复习笔记1-java基础_第6张图片

内存泄漏和内存溢出区别

内存泄漏: 就是内存申请后,用完没有释放,造成可用内存越来越少。
内存溢出: 就是申请内存时,JVM 没有足够的内存空间。
两者关系:内存泄露 → 剩余内存不足 → 后续申请不到足够内存 →内存溢出。

常见的内存泄漏

1、静态集合类,如 HashMap、ArrayList 等。如果这些容器为静态的(static),它们的生命周期与程序一致,不会被回收
2、各种连接,如数据库连接、网络连接和 IO 连接等,如果不 close,会造成大量的对象无法被回收,在Java 7中,当自动关闭所有类型的 Stream 的功能被置入到 try-with-resource clause 中时,忘记关闭Stream这事就基本上不会存在了
3、将【没有】 hashCode()和 equals()的【业务对象】添加到 HashSet 中,(集合元素唯一,但是每重写,存入的对象都是地址值不相同的),HashMap存的 key 为对象且也没有 hashcode 和 eques方法也会有同样的问题
4、ThreadLocal 列在 Java 内存泄露的榜首,总能在不知不觉中将内存泄露掉,一个常见的例子是:

@Test
public void testThreadLocalMemoryLeaks() {
    ThreadLocal<List<Integer>> localCache = new ThreadLocal<>();
   List<Integer> list= new ArrayList<>(10000);
    localCache.set(list);
    localCache = new ThreadLocal<>();
}

ThreadLocal 内部维护了一个 Map (ThreadLocalMap )提供了 get()、set() 方法,key 的弱引用,在垃圾回收的时候会被清理掉的,但 value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value,会造成内存泄漏,

当 localCache 的值被重置之后, list 被 ThreadLocalMap 中的 value 引用,无法被GC,但是其key对ThreadLocal实例的引用是一个弱引用,本来ThreadLocal的实例被localCache和ThreadLocalMap的key同时引用,但是当localCache的引用被重置之后,则ThreadLocal的实例只有ThreadLocalMap的key这样一个弱引用了,此时这个实例在GC的时候能够被清理。

关于内存使用的建议

1、尽早释放无用对象的引用,让引用变量在退出活动域后自动设置为 null。
2、程序进行字符串处理时,尽量避免使用 String,而使用 StringBuffer。
3、静态变量是全局的,GC 不会回收。
4、避免集中创建对象尤其是大对象,如果可以的话尽量使用流操作。

JVM 会突然需要大量内存,这时会触发 GC 优化系统内存环境。

m_totalBytes = m_request.getContentLength();
m_binArray = new byte[m_totalBytes];
m_totalBytes 这个变量特别大,导致数组分配很大的内存空间,而且该数组不能及时释放。

5、尽量运用对象池技术以提高系统性能。
生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。

6、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。
可以适当的使用 hash 创建一组对象容器,然后从容器中去获取对象,而不用每次new之后又丢弃。

7、优化配置。

常见的OOM(内存溢出)

可以对程序日志中的 OutOfMemoryError 配置关键字告警,一经发现,立即处理
复习笔记1-java基础_第7张图片

1. java.lang.OutOfMemoryError: Java heap space

当堆内存(Heap Space)没有足够空间存放新创建的对象时,就会抛出该异常
1. 内存设置过小,则通过 -Xmx 参数调高 JVM 堆内存空间即可
2. 检查代码是否创建了一个超大对象,通常是一个大数组,比如查询了数据库全部结果,没有做结果数限制。
3. 请求压力过大,可以考虑添加机器资源,或者做限流降级。

2. java.langOutOfMemoryError:Metaspace

一般是加载到内存中的 class 数量太多或者体积太大
Java8 及以后已经使用 Metaspace 来替代永久代(存放class 定义,包括类的名称,字段,方法和字节码和常量池),占用的是本地内存。
通过 JVM 参数 -XX:MaxMetaspaceSize 调整

3. StackOverflowError

线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常 。递归调用方法,如果没有方法出口的方法会造成发生

4. java.lang.OutOfMemoryError:GC overhead limit exceeded

超过 98% 的时间用来做 GC 并且回收了不到 2% 的堆内存,连续 5 次 GC 都只回收了不到 2% 的情况下才会抛出。应用程序已经基本耗尽了所有可用内存
此类问题的原因与解决方案跟 Javaheap space 非常类似

5. java.lang.OutOfMemoryError:Direct buffer memory

Direct memory可以通过参数-XX:MaxDirectMemorySize设定本机直接内存可用大小,如果不指定,则默认与java堆内存大小相同。过使用-XX:MaxDirectMemorySize=5M,限制最大可使用的本机直接内存大小为5MB,如果超过将抛出异常。

6. java.lang.OutOfMemoryError:unable to create new native thread

并发请求服务器时,经常出现该异常
1.创建太多的线程了
2.服务器的设置限制了你创建线程的数量了

for循环删元素
不要在 foreach 循环里进行元素的 remove/add 操作,要用I terator it = list.iterator();,且 remove 必须要用 iterator 的 remove 方法,ConcurrentModificationException

Overloaded (重载)的方法的参数列表不一样,它们的返回者类型可以不一样。两个方法参数列表,返回值不同也不行

try 里面有 return 会暂存,执行 finally 里的在 return,如果 finally 里有 return 就不会执行 try 里的 return,finally 中有 return 在编译器中会警告。

异常
Throwable 类有两个重要的子类 Exception(异常,代码可处理捕获)和 Error(错误,代码处理不了)
exception 分 检查异常和运行时异常
检查异常(check-exception)
除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundException 、SQLException…

运行时异常(RuntimeException 或者叫不受检查异常)
编译不报错,可能在运行时报错,RuntimeException 及其子类都统称为非受检查异常,例如:NullPoin​terException、NumberFormatException、ArrayIndexOutOfBoundsException、ClassCastException、ArithmeticException等。

数组和集合互相转换

List myList = Arrays.asList(“Apple”,“Banana”, “Orange”);
String[] array2=list.toArray(new String[0]);
注意:asList()传入的对象必须是对象数组,【不能是基本类型的数组】,当传入一个原生数据类型数组时,Arrays.asList() 的真正得到的参数就不是数组中的元素,而是数组对象本身

DI依赖注入(注入对象)的几种实现方式

1. 即构造方法注入 
2.  setter注入 
3.  @Autowired 按 byType 自动注入,而 @Resource 默认按 byName 自动注入

深拷贝与浅拷贝

B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,如果B没变,那就是深拷贝,实现对象拷贝的类,需要实现 Cloneable 接口,并覆写 clone() 方法。

多线程的实现方式

考虑【单继承多实现】和是否【需要返回值和抛异常】
1.继承 Thread 类 调用 start 方法,获取到资源后会执行 run 方法 (单继承)

 MyThread extends Thread
 new MyThread().start();

2.实现 Runnable 接口,Java 1.0 中引入,不会返回结果或抛出检查异常 (多实现)

MyRunnable implements Runnable
new Thread(new MyRunnable ()).start(); # thread 参数为 runable

3.实现 Callable 结合 FutureTask ,Java 1.5 中引入,(可以返回结果或抛出检查异常)

  public class ThirdThread implements Callable<Integer>{
  
    @Override
    public Integer call(){
    	//TODO 业务操作
      return ticket;
    }
  }
 
  private void starCallableThread(){
    ThirdThread thirdThread = new ThirdThread();
    FutureTask<Integer> task = new FutureTask<Integer>(thirdThread);
    new Thread(task,"有返回值的线程").start();
    Integer integer = task.get();
  }

4.使用线程池接口 ExecutorService 结合 Callable、Future 实现有返回结果的线程

不允许使用 Executors 类去创建线程池,因为如下

FixedThreadPool 和 SingleThreadExecutor : 允许请求队列(任务队列)长度 Integer.MAX_VALUE ,堆积大量请求OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建线程数量为 Integer.MAX_VALUE ,可能创建大量线程,导致 OOM。

FixedThreadPool : 固定线程数量池。线程数量不变。当有新任务提交时,若有空闲线程,立即执行。满了新任务会被暂存在任务队列中,有线程空闲时,便处理在任务队列中的任务。
SingleThreadExecutor: 只有一个线程的线程池。多余任务被保存在一个任务队列中,线程空闲,先入先出的顺序执行任务。
CachedThreadPool: 线程池的线程数量不确定,当有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
ScheduledThreadPool:定期执行任务。 这个在实际项目中基本不会被用到,因为有其他方案选择比如quartz

而是通过 ThreadPoolExecutor 的方式
ThreadPoolExecutor 3 个最重要的参数:

1. corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
2. maximumPoolSize : 当【任务队列已满时】的时候,当前可以同时运行的线程数量变为最大线程数。
3. workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

ThreadPoolExecutor其他常见参数:

1. keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
2. unit : keepAliveTime 参数的时间单位。
3. threadFactory :executor 创建新线程的时候会用到。
handler :饱和策略。关于饱和策略下面单独介绍一下。
4. ThreadPoolExecutor 饱和策略,如下

如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,ThreadPoolTaskExecutor 定义一些策略:

1. (默认)抛异常 ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。调用者可以捕获这个异常,根据需求编写自己的处理代码。
2. ThreadPoolExecutor.CallerRunsPolicy**:调用者运行策略,由主线程,也就是直接在调用 execute 方法的线程中运行被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
3. ThreadPoolExecutor.DiscardPolicy: 抛弃策略,不处理新任务,直接丢弃掉。
4. ThreadPoolExecutor.DiscardOldestPolicy: 抛弃最旧的策略,丢弃最早的未处理的任务请求。

对于可伸缩的应用程序,建议使用 ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时,此策略为我们提供可伸缩队列

复习笔记1-java基础_第8张图片

ThreadLocal

ThreadLocal 对象可以提供线程局部变量,每个线程 Thread 拥有一份自己的副本变量,多个线程互不干扰。
ThreadLocal 内部维护了一个 Map (ThreadLocalMap )提供了 get()、set() 方法,key 的弱引用,在垃圾回收的时候会被清理掉的,但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value,会造成内存泄漏,每次使用完 ThreadLocal 都调用它的 remove() 方法清除数据
ThreadLocalMap 类似 HashMap 的结构,HashMap 是由数组+链表实现的,而 ThreadLocalMap 并没有链表结构
ThreadLocalMap 有自己的独立实现,可以简单地将它的 key 视作 ThreadLocal,value 为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。

volatile

volatile主要是用来在多线程中同步变量
volatile只能确保操作的是同一块内存,并不能保证操作的原子性。一般用于声明简单类型变量,使得这些变量具有原子性,即一些简单的赋值与返回操作将被确保不中断。但是当该变量的值由自身的上一个决定时,volatile 的作用就将失效

 1.保证可见性,不保证原子性
  (1)当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
  (2)这个写会操作会导致其他线程中的 volatile 变量缓存无效。
 2.禁止指令重排

线程和进程

一个进程(如java进程)中可以有多个线程(处理业务),多个线程间共享进程(java内存中)的堆和方法区 
(JDK1.8 之后的元空间)资源,但每个线程有自己的【程序计数器、虚拟机栈和本地方法栈】。一个程序至少有一个进程,一个进程至少有一个线程.
总结: 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。

堆和方法区

堆和方法区是所有线程共享的资源,其中堆是【进程】中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),
方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

线程死锁及如何避免死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

线程 A 持有资源 1,线程 B 持有资源 2,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁

产生死锁必须具备以下四个条件

1、互斥: 		该资源任意一个时刻只由一个线程占用
2、占有且等待:	 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占: 	别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待: 	存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

如何预防和避免线程死锁

破坏死锁的产生的必要条件即可:

破坏请求与保持条件 :一次性申请所有的资源。
破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

Java 中的线程的生命周期大体可分为6种状态

1. 初始(NEW):新创建了一个线程
2. 运行(RUNNABLE):就绪(ready)和运行中(running)笼统的称为 RUNNABLE,调用 start 后处于ready 状态,在获得CPU 时间片后变为运行中状态(running)。
3. 阻塞(BLOCKED):没有获取到锁的情况下,处于 BLOCKED(阻塞)
4. 等待(WAITING):执行 wait 后处于 WAITING 状态
5. 超时等待(TIMED_WAITING):该状态不同于 WAITING ,它可以在指定的时间后自行返回。
6. 终止(TERMINATED):表示该线程已经执行完毕。

复习笔记1-java基础_第9张图片

sleep() 方法和 wait() 方法区别和共同点

两者最主要的区别在于:sleep 方法没有释放锁,而 wait 方法释放了锁 
调用 wait 后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify 或者 notifyAll 方法。
调用 sleep 方法执行完成后,线程会自动苏醒,或者可以使用 wait(long timeout) 超时后线程会自动苏醒。

为什么调用 start() 方法时会执行 run() 方法,为什么不直接调 run()

new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 
start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 如果直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行

总结: 调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。

反射及应用场景

1.概念

反射:是指在运行状态中,
1.对任意类,都能够知道这个类的所有属性和方法;
2.对于任意一个对象,都能够调用它的任意一个方法和属性

2.获取 Class 对象的四种方式

1.知道具体类的情况下可以使用:

Class alunbarClass = TargetObject.class;
但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化

2.通过 Class.forName()传入类的路径获取:

Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");

3.通过对象实例 instance.getClass() 获取:

TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();

4.通过类加载器xxxClassLoader.loadClass()传入类路径获取:

Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");
		// 获取TargetObject类的Class对象并且创建TargetObject类实例
        Class<?> tagetClass = Class.forName("cn.javaguide.TargetObject");
        TargetObject targetObject = (TargetObject) tagetClass.newInstance();
        // 获取所有类中所有定义的方法
        Method[] methods = tagetClass.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }

3.应用场景

概述:平时用不到,框架用到,jdk代理,注解
1.平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
2.像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。JDK动态代理其实是反射机制实现的aop动态代理
3.这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
4.像 Java 中的一大利器【注解】的实现也用到了反射。

为什么你使用 Spring 的时候 ,一个 @Component注解就声明了一个类为 Bean ,@Value 注解就读取到配置文件中的值呢
这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解

如 MyBatis 中接口和 xml 配合使用,只需在接口里定义好方法,不用写实现类,然后在对应的 xml 文件中写好与 sql 相关的配置就可以,sql 语句和 java 代码解耦,

jvm 对类(Class)的加载过程

文件需要加载到虚拟机中之后才能运行和使用,加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
复习笔记1-java基础_第10张图片

一、加载
将 class 文件加载至 java 虚拟机,并存储在方法区。方法区存储类信息、常量、静态变量。

二、验证
确保加载进来的 class 文件包含的信息是否符合 java 虚拟机的要求。是否合法、是否安全。验证项目包括:文件格式验证、元数据验证、字节码验证、符号引用验证。

三、准备
为类变量分配内存,并设置类变量的初始值。
进行内存分配的包括类变量( static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在 java 堆中。
其次是这里所说的初始值“通常情况”下是数据类型的零值(如 0、0L、null、false 等)。

四、解析
将变量池里符号引用转为直接引用。

五、初始化
初始化类变量、静态语句块。

在以下四种情况下初始化过程会被触发执行:

遇到 new、getstatic、putstatic 或 invokestatic 这4条字节码指令时,如果类没有进行过初始化,则需先触发其初始化;
使用 java.lang.reflec t包的方法对类进行反射调用的时候;
当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化;
jvm 启动时,用户指定一个执行的主类(包含 main 方法的那个类),虚拟机会先初始化这个类

六、使用
使用类

七、卸载
执行了System.exit()方法
程序正常执行结束
程序在执行过程中遇到了异常或错误而异常终止
由于操作系统出现错误而导致Java虚拟机进程终止

java 和 python 区别

从大的方面说:
python 拥有大量的计算第三方库,更适合科学计算,数据分析等研究工作,而java 更适合商业开发
python更灵活,无需事先申明变量数据类型

从细节上说:
1、java 单继承,而python 多继承
2、python只有四种数据:整数,长整数、浮点数和复数 java 则有 char,short,byte,int,long,float,double 类型
Python有五个数据类型:Numbers(数字)、String(字符串)、List(列表)、Tuple(元组)、Dictionary(字典)

二叉树

复习笔记1-java基础_第11张图片
叶子节点:度为0的结点称为叶结点,也可以叫做终端结点

二叉树:每个结点最多有两个子树的有序树,子树的根被称作左子树和右子树。
满二叉树:每一个层的结点树都达到最大值
完全二叉树:叶节点只能出现在最下层和次下层,且最下面一层的结点都集中在该层最左边的的二叉树(满二叉树的任意节点,要么度为0,要么为2)

二叉查找树:特殊的二叉树,左子树节点比父节点小,右子树节点值比父节点大。
平衡二叉树:或AVL树,是为了解决二叉树退化成一棵链表(大部子节点比父节点小)而诞生的。每个节点的左子树和右子树的高度差至多等于1。
红黑树:每个节点都带有颜色属性的自平衡二叉查找树,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能

二叉查找树的极端情况,大部分子节点都比父节点值小,然后导致所有的数据偏向左侧,进而退化成链表,如下图所示:
复习笔记1-java基础_第12张图片
平衡二叉树:使用【二叉查找树】的目的是因为其效率高于【链表】查询,为解决查找树退化成一棵链表引入了平衡二叉树
复习笔记1-java基础_第13张图片

红黑树的特点:

具有二叉树所有特点。
每个节点只能是红色或者是黑色。
根节点只能是黑色,且黑色根节点不存储数据。
任何相邻的节点都不能同时为红色。
红色的节点,它的子节点只能是黑色。
从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
红黑树如下图所示:
复习笔记1-java基础_第14张图片
为什么有了数组和链表还要引入二叉树

针对数组和链表的优缺点,无法说链表一定优于数组,或者是数组一定优于链表,因为某些长期的需要,所以就推出一个相对折中的二叉树。

为什么有了二叉树还要引入平衡二叉树

二叉树有一种极端的情况,就是所有的子结点偏向一端,二叉树退化成链表,解决退化为链表的情况,引入了平衡二叉树,

为什么有了平衡二叉树还要引入红黑树

所以对于搜索、插入、删除操作较多的情况下,红黑树的效率是优于平衡二叉树的。

二叉树的遍历方式:前序、中序、后序、层序
前中后是以根节点为视角的遍历
复习笔记1-java基础_第15张图片
前序遍历:根-左-右
复习笔记1-java基础_第16张图片
中序遍历:左-根-右
复习笔记1-java基础_第17张图片
后序遍历:左-右-根
复习笔记1-java基础_第18张图片

层序遍历:从根节点出发,依次访问左右孩子结点,再从左右孩子出发,依次它们的孩子结点,直到节点访问完毕
复习笔记1-java基础_第19张图片

五层协议栈

物理层、链路层、网络层、传输层、应用层

TCP/IP 分别在模型的哪一层

TCP 在传输层(运输层)
IP 在网络层(互联网层)

http 和 https 区别

HTTP 是明文的不安全的,而 HTTPS 是加密安全的
HTTP 标准端口是 80 ,而 HTTPS 的标准端口是 443
HTTP 无需认证证书,而 https 需要认证证书 

Integer作比较使用==和equals的区别

==只适用在-128至127之间进行判断,超过这个范围就会失效。用 equals 方法却可以,源码把传入的参数强转成为 int 类型

Java的两个高精度计算类

BigInteger支持任意精度的整数,比如:任何大小的整数值。
BigDecimal支持任意精度的定点数,比如:精确的货币计算。
浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。 具体原理和浮点数的编码方式有关
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999964
System.out.println(a == b);// false
我们在使用 BigDecimal时,为了防止精度丢失,推荐使用它的 BigDecimal(String) 构造方法来创建对象

char 和 varchar

char的长度是不可变的,而 varchar 的长度是可变的(根据实际存值节省空间)
char最多可以存放255个字符, varchar的最大长度为65535个字节,

Spring 框架中用到的设计模式

java 有 23 种设计模

工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
......
单例模式有 懒汉式单例、饿汉式单例。有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有其他几种写法。

单例模式优缺点

优点
(1)在内存中只有一个实例,减少内存开支
缺点
(1) 单例模式没有抽象层,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
(2) 单例类的职责过重,在一定程度上违背了“单一职责原则”。
(3) 滥用单例将带来一些负面问题,如:为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出; 

tcp和udp

tcp:需要连接,占用资源少,保证数据顺序和准确,不会丢包
udp:不需要连接,占用资源多,不保证数据顺序和正确,会丢包

tcp三次握手四次挥手

第一次握手:客户端向服务端发消息说我要跟你通信 SYN=1
第二次握手:服务端收到后,给客户端发消息我准备好了 ACK
第三次握手:客户端收到后,给服务端发消息我也准备好了 ACK

第一次挥手:客户端告诉服务端“我说完了(FIN)”,此时自身形成等待结束连接的状态。
第二次挥手:服务端知道后,回复客户端说,“我知道你说完了(ACK)”。
第三次挥手:客户端告诉客户端,“我说完了,咱们断了吧(FIN)”。
第四次挥手:客户端收知道服务端也说完了,也要告诉服务端一声(ACK),因为连接和断开要双方都按下关闭操作才能断开,客户端同时又为自己定义一个定时器,因为不知道刚才说的这句话能不能准确到达服务端(网络不稳定或者其他因素引起的网络原因),默认时间定为两个通信的最大时间之和,超出这个时间就默认服务器端已经接收到了自己的确认信息,此时客户端就关闭自身连接,服务器端一旦接收到客户端发来的确定通知就立刻关闭服务器端的连接。

声明:断开链接不一定就是客户端,谁都可以先发起断开指令,客户端和服务端没有固定标准,谁先发起请求谁就是客户端。

synchronized 和 Lock

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础,线程访问同步代码块时,它首先是需要得到锁,当退出或者抛出异常时必须要释放锁

普通方法:锁是当前实例对象
静态方法,锁是当前类的 class 对象
方法块:锁是括号里面的对象 

Lock lock=new ReentrantLock();

synchronized:是java关键字、可自动释放锁、阻塞、可重入、不可中断、非公平、悲观锁
Lock(ReentrantLock):是java类、不能自动释放锁、非阻塞、可重入、等待时可中断、可判断、可公平可非公平、乐观锁

可重入锁:当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁(上锁的次数加1),而其他的线程是不可以的

类别 synchronized Lock
存在层次 java关键字 是类
是否能自动释放锁 获得锁执行完代码后自动释放,异常也可以自动释放,不会出现死锁 发生异常时候,不会主动释放锁,要手动 unlock 释放,可能会死锁。(用 try catch 包起来,finally 中写入 unlock,避免死锁)
是否能中断 不可中断,除非代码异常或正常执行完成 可中断,可通过 trylock(long timeout,TimeUnit unit)设置超时方法或者将 lockInterruptibly()放到代码块中,调用 interrupt 方法进行中断。
是否公平 非公平锁 通过构造方法 new ReentrantLock 传 boolean 值选择是否公平,默认为空 false 非公平锁
是否阻塞 获取不到锁一直阻塞 非租塞获取不到锁,线程不用一直等待就结束了

你可能感兴趣的:(java)