0Java基础

1.Java基础篇

1.1基本

a.JDK 和 JRE 有什么区别?
JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环境。
  具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK
b.== 和 equals 的区别
== 解读
对于基本类型和引用类型 == 的作用效果是不同的,如下所示:
基本类型:比较的是值是否相同;
引用类型:比较的是引用是否相同;
​
equals 解读
equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。
c.两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
不对,两个对象的 hashCode() 相同,equals() 不一定 true。
注:hashCode()可以看作散列值
d.Java 中的 Math. round(-1. 5) 等于多少?
等于 -1,因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃。

临界值:向右取整原则 

区间原则:左闭右开原则

想起工作中遇到过的曾百思不得其解的问题,以为是我的算法有问题:

计算中有个容易忽略的问题,只有公式中有浮点数(除数或者被除数)这个取整函数才有效

e.String 属于基础的数据类型吗?
String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象。

注:字节数----一拜不洽byte、boolean、char,二秀short,三映佛int、float,四浪搭long、double,不定斯君String。

为什么同字节数?浮点数范围更大,因为IEEE754标准。 

技术特征

7 位(bits)表示一个字符,共 128 字符

ASCII 扩展字符集

7 位编码的字符集只能支持 128 个字符,为了表示更多的欧洲常用字符,对 ASCII 进行了扩展,ASCII 扩展字符集使用 8 位(bits)表示一个字符,共 256 字符。

ASCII知识icon-default.png?t=N7T8https://baijiahao.baidu.com/s?id=1704767913015693638&wfr=spider&for=pc

 常记ASCII码:

0:48

A:65   +32  Z:90   a:97   z:122

f.如何将字符串反转?
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
g.String 类的常用方法都有那些?

indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。

h.抽象类必须要有抽象方法吗?
不需要,抽象类不一定非要有抽象方法。
i.普通类和抽象类有哪些区别?
普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化,普通类可以直接实例化。

2.接口与抽象

https://www.cnblogs.com/dolphin0520/p/3811437.html

  1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

  2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

  3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;

  4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

抽象类是整个类整体进行抽象,包括属性、行为抽象,

接口是对行为的抽象。

懒汉式 就是用到才加载

饿汉式 一开始就会加载

3.封装、继承和多态

封装可以隐藏实现细节,使得代码模块化;

继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。子类继承父类的特征和行为,使得子类具有父类的各种属性和方法。

多态则是为了实现另一个目的——接口重用!同一个实现接口,使用不同的实例而执行不同的操作。不同的对象可以执行相同的操作,但要通过他们自己的实现代码来执行。

队列用过 主要做排队

先进先出

4.堆、栈、方法区

堆区(heap):1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

栈区(stack):1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

方法区(method):1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

6.Static

​ 被static修饰的成员变量存储在方法区内的只是一个引用,而实际的值,如果是原始类型就存储在栈区(原始类型的成员变量存储在堆区中的实例数据中),而引用类型就存储在堆区

​ 如果一个类中有静态变量的话,程序首先会把该静态变量加载进内存中,也就是在堆中开辟一个区域专门存放。以后不管你new多少个类的对象,该静态变量永远都是在那里的。也就是说,静态变量在类的初始化一次后,系统就不会为该变量开辟新的内存空间。

​ 而每new一个类的对象,系统就会重新在堆内存中开辟一个新空间来存放该类的实例对象,并且栈中也会有一个新的引用变量去指向它。

​ 静态方法也是类似,但是有一点要强调,静态方法只中不能调用非静态方法。因为被static修饰的方法会首先被Classloader对象先加载进内存,而这个时候可能其它的非静态方法或者变量还没有被加载进来。

​ 被static修饰过的都是随着类的初始化后就产生了,在堆内存中都有一块专门的区域来存放,所以只需要类名点方法名或者变量名即可。而非静态的就必须通过类的对象去调相应的。

5.反射

https://blog.csdn.net/a745233700/article/details/82893076

​ 什么是反射:

​ 反射就是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

​ 反射的三种获取方式:getClass(都new出来了还要反射出来干嘛) .class(需要导入类包) Class.forname(全限定名)getInstance(User.class)

    //第一种方式获取Class对象  
    Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
    Class stuClass = stu1.getClass();//获取Class对象
    
    //第二种方式获取Class对象
    Class stuClass2 = Student.class;
    System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
    
    //第三种方式获取Class对象
    try {
        Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
        System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    
// 注意,在运行期间,一个类,只有一个Class对象产生,所以打印结果都是true;
​
三种方式中,常用第三种,第一种对象都有了还要反射干什么,第二种需要导入类包,依赖太强,不导包就抛编译错误。一般都使用第三种,一个字符串可以传入也可以写在配置文件中等多种方法。

通过得到class对象,反向获取student对象的各种信息。

反射的优缺点:

​ 1、优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

​ 2、缺点:(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;

​ (2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

反射的用途:

​ 1、反编译:.class-->.java

​ 2、通过反射机制访问java对象的属性,方法,构造方法等

​ 3、当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。

​ 4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。

​ (1)在运行时判断任意一个对象所属的类;(2)在运行时构造任意一个类的对象;(3)在运行时判断任意一个类所具有的成员变量和方法;(4)在运行时调用任意一个对象的方法

6.类加载机制、双亲委派机制

https://blog.csdn.net/a745233700/article/details/90232862

https://baijiahao.baidu.com/s?id=1663852327367330121&wfr=spider&for=pc

类加载机制

​ 就是虚拟机把类的数据从class文件加载到内存,并对数据进行校检,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程。

加载:

将class文件加载到内存中,并在方法区创建对应的class对象

验证:

​ 校验加载的class文件是否符合字节码规范

准备:

jvm开始为类变量分配内存并初始化零值。

​ 类变量指被static修饰的变量。

​ 如果一个变量是常量(被 static final修饰)的话,那么在准备阶段,属性便会被赋予用户希望的值。因为final修饰 的值一旦被赋值就不能再更改。

解析:

​ JVM 针对类或接口、字段、类方法、接口方法、方法类型等7类引用进行解析。这个阶段的主要任务是将其在常量池中的符号引用替换成直接其在内存中的直接引用。

初始化:

​ 到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。在这个阶段,JVM 会根据语句执行顺序对类对象进行初始化

使用:

​ 当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。

卸载:

​ 当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存。

双亲委派机制

​ 双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父类加载器。(类加载器之间的父子关系不是以继承的关系实现,而是使用组合关系来复用父加载器的代码)

1、双亲委派模型的工作原理:

​ 如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层级的类加载器都是如此,因此所有请求最终都会被传到最顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

​ 因此,加载过程可以看成自底向上检查类是否已经加载,然后自顶向下加载类。

2、双亲委派模型的优点:

​ (1)避免类的重复加载,当父类加载器已经加载了该类时,子类加载器就没必要再加载一次。

​ (2)使用双亲委派模型来组织类加载器之间的关系,Java类随着它的类加载器一起具备了一种带有优先级的层次关系。

​ (3)解决各个类加载器的基础类的统一问题,越基础的类由越上层的加载器进行加载。避免Java核心API中的类被随意替换,规避风险,防止核心API库被随意篡改。

例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。

9.重载与重写

重载Overload:表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同,参数个数或类型不同
重写Override:表示子类中的方法可以与父类中的某个方法的名称和参数完全相同

懒汉式

和饿汉式 一开始

1.创建一个枚举类

INSTANCE; 使用这个关键字 
编写对应的方法

2.Java容器篇

27集合容器概述及基本数据结构_邻接表是散列吗_Java_Eastlin的博客-CSDN博客

28java容器方法概述(第一级结构)-CSDN博客

29java容器方法概述(第二级结构)-CSDN博客

2.1基本

Java 容器都有哪些? 集合
16. Java 容器分为 Collection 和 Map 两大类,其下又有很多子类,如下所示:
Collection

List
ArrayList
LinkedList
Vector
Stack

Set
HashSet
LinkedHashSet
TreeSet

Map
HashMap
LinkedHashMap
TreeMap
ConcurrentHashMap
Hashtable
List、Set、Map 之间的区别是什么?
List
    有序且重复
Set
    无序且不可重复
Map
    key唯一,值可以不唯一
HashMap 和 Hashtable 有什么区别?三方面
存储:HashMap 允许 key 和 value 为 null,而 Hashtable 不允许。
线程安全:Hashtable 是线程安全的,而 HashMap 是非线程安全的。
推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。
ArrayList 和 LinkedList 的区别是什么?
数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
  综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
如何实现数组和 List 之间的转换?
数组转 List:使用 Arrays. asList(array) 进行转换。
List 转数组:使用 List 自带的 toArray() 方法。

## 1. LIST

> ArrayList  线程不安全

不安全原因:

​        1.一个线程在遍历列表,一个线程在修改列表。会报错ConcurrentModificationException。

​        2.多线程插入操作,没有同步操作。容易丢失数据。

一般都使用增强for循环

解决方法:

​    1.使用Vector(ArrayList所有方法加synchronized)

​    2.使用Collections.synchronizedList() 转换为线程安全类

​    3.CopyOnWriteArrayList读写分离

主要解决:

​    1.在判断是否扩容时,多线程会误判没有扩容,导致索引越界。

​    2.在进行下一步elementData[s]=e,s=s+1的时候,如果多个线程在elementData[s]=e造成值覆盖,就会造成数据丢失问题,也是为什么集合添加值得时候出现null

> Vector 线程安全,不建议使用

他本质是ArrayList,所有方法上都加了同步锁。严重影响效率。

> Collections.SynchronizedList

锁定的是代码块。在执行add()等方法的时候是加了synchronized关键字的,但是listIterator(),iterator()却没有加.所以在使用的时候需要加上synchronized.

> copyOnWriteArrayList

​    在写操作(add,remove,set)时,先复制一个新数组,长度为原来数组长度+1,然后将新数组最后一个元素添加元素。

​    而读操作读到的是修改之前的数组,不会产生线程安全问题。导致数据一致性的问题。

**在不要求数据实时一致性的情况下,读不加锁,写加锁,使用copyOnWriteArrayList以提高性能。**

## 2. MAP

> HashMap  数组加链表  扩容后变奇偶子链条 

底层是数组加链表表的结构,当要添加一个key-value时,首先计算key的hash值,插入数组。如果有hash重复的则往后面添加形成链表。当链表超过8个转为红黑树。提高查询速度。

默认16  ,加载因子0.75 当达到12个时扩容。扩容后将链条分为奇偶子链条。

HashMap:hashmap中的key和value值都允许为null,线程不安全   默认长度16。实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。

HashTable:在方法上添加了Synchronized关键字,锁住了整个方法,并发度很低。hashtable的key和value值都不允许为null。  默认长度11

​        jdk1.7是是头插法,多个线程去操作这个Entry,因为resize()方法的赋值方式,也就是使用了单链表的头插入方式,**同一位置上新元素总会被放在链表的头部位置**,当需要扩容时,**在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上,出现B的下一个指针指向了A**,一旦几个线程都调整完成,就可能出现环形链表。 比如一开始设想的A->B->C,  扩容后B->A

> ConcurrentHashMap

**CAS与synchronized**结合的同步方式,并在node部分(元素结点处)添加**volatile**关键字,保证了get方法时的可见性。


​    synchronized**只锁定了当前链表或红黑二叉树的首节点,也就是说只有发生了hash不冲突,才会触发synchronized同步机制。在效率上又会有提升。**

put操作如下:

1. 根据 key 计算出 hashcode 。
2. 判断是否需要进行初始化。
3. 为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
4. 如果当前位置的 触发扩容机制,则需要进行扩容。
5. 如果都不满足,则利用 synchronized 锁写入数据。
6. 如果数量大于 默认链表长度 则要转换为红黑树。

## 3. list 和set 的区别

List可以包含重复元素,而Set包含唯一项。
List是一个有序集合,它维护插入顺序,而Set是一个无序集合,不保留插入顺序。
List接口包含一个遗留类:Vector类,而Set接口没有任何遗留类。
List接口可以允许n个null值,而Set接口只允许一个null值。

堆 

## Arraylist与LinkedList区别

可以从它们的底层数据结构、效率、开销进行阐述哈

ArrayList是数组的数据结构,LinkedList是链表的数据结构 

随机访问的时候,ArrayList的效率比较高,因为LinkedList要移动指针,而ArrayList是基于索引(index)的数据结构,可以直接映射到。

插入、删除数据时,LinkedList的效率比较高,因为ArrayList要移动数据。 

LinkedList在大量插入和删除

数据结构不同    LinkedList 从头部和尾部开始   所以中间的数据 操作比较慢  

双向链表结构

LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用。

 一直看着屏幕 不要这样 他看不出来的

LinkedList对首尾操作比较快


## 4.HashSet和TreeSet的区别?

HashSet不保持顺序,而TreeSet维持升序。
HashSet由哈希表表示,而TreeSet由树结构实现。
HashSet比TreeSet执行得更快。
HashSet由HashMap支持,而TreeSet由TreeMap支持。

***\*[1, 2, 3, 4]                 //TreeSet内部自动排序 采用自然排序,使用树结构存储Set接口的实现,并以升序顺序排序\****

![image-20210831155011416](C:\Users\10630\AppData\Roaming\Typora\typora-user-images\image-20210831155011416.png)

2.区别:

* 1.HashMap是线程不安全的,效率高,JDK1.2版本
* Hashtable是线程安全的,效率低,JDK1.0版本
* 2.HashMap可以存储null键和null值
* Hashtable不可以存储null键和null值

### **HashTable** 

- 底层数组+链表实现,无论key还是value都**不能为null**,线程**安全**,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
- 初始size为**11**,扩容:newsize = olesize*2+1
- 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

### **HashMap**

- 底层数组+链表实现,可**以存储null键和null值**,线程**不安全**
- 初始size为**16**,扩容:newsize = oldsize*2,size一定为2的n次幂
- 扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
- 插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
- 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
- 计算index方法:index = hash & (tab.length – 1)

### **ConcurrentHashMap**

- 底层采用分段的数组+链表实现,线程**安全**
- 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
- Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
- 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
- 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容

## 5. hashMap的put原理 hashmap的原理 

1.底层存储数据的数组,在第一次put元素的时候初始化,同时发生第一次扩容;

2.相比较JDK 1.8之前的版本,JDK 1.8在链表长度大于8的时候,会转化为红黑树处理,主要是基于效率的考量;

不安全红黑树  数据量多的时候 查询速度比较快 

链表是用来解决hash冲突问题,当出现hash值一样的情形,就在数组上的对应位置形成一条链表。

![img](https://img-blog.csdnimg.cn/20190531101056722.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTI4MzIxMg==,size_16,color_FFFFFF,t_70)

### ConcurrentHashMap

线程安全:简单理解就是,ConcurrentHashMap 是一个 数组, 数组通过继承 Reentrant Lock(可重入锁) 来进行加锁,所以每次需要加锁的操作锁住的是一个 数组,这样只要保证每 个 Segment 是线程安全的,也就实现了全局的线程安全。

### **ConcurrentHashMap**

- 底层采用分段的数组+链表实现,线程**安全**
- 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
- Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
- 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
- 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容

3.线程篇

3.1基本

#     线程和并发 

### 1.线程的三种启动方式  

- 继承 Thread   重写 run 方法;
- 实现 Runnable 接口;              没有返回值
- 实现 Callable 接口。                 有返回值

并行和并发有什么区别?
并行:多个处理器或多核处理器同时处理多个任务。
并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。
​
并发 = 两个队列和一台咖啡机。
并行 = 两个队列和两台咖啡机。
线程和进程的区别?
一个程序下至少有一个进程,一个进程下至少有一个线程,一个进程下也可以有多个线程来增加程序的执行速度。

创建线程有哪几种方式?
创建线程有三种方式:
继承 Thread 重新 run 方法;
实现 Runnable 接口;
实现 Callable 接口。
说一下 runnable 和 callable 有什么区别?
runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。

线程的 run() 和 start() 有什么区别?
start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。
run() 可以重复调用,而 start() 只能调用一次。
什么是死锁?
	当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。


怎么防止死锁?
尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
尽量使用 Java. util. concurrent 并发类代替自己手写锁。
尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
尽量减少同步的代码块。
sleep() 和 wait() 有什么区别?
类的不同:sleep() 来自 Thread,wait() 来自 Object。
释放锁:sleep() 不释放锁;wait() 释放锁。
用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。

### 6.线程池中 submit() 和 execute() 方法有什么区别?

- execute():只能执行 Runnable 类型的任务。
- submit():可以执行 Runnable 和 Callable 类型的任务。

### 7.在 Java 程序中怎么保证多线程的运行安全?

- 方法一:使用安全类,比如 Java. util. concurrent 下的类。
- 方法二:使用自动锁 synchronized。
- 方法三:使用手动锁 Lock。

~~~
手动锁实例
Lock lock = new ReentrantLock();
lock.lock();
try {
    System. out. println("获得锁");
} catch (Exception e) {
    // TODO: handle exception
} finally {
    System. out. println("释放锁");
    lock. unlock();
}
~~~

### 8.什么是死锁,怎么防止死锁?

​        **死锁:**当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

​        **防止死锁:**

- 尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
- 尽量使用 Java. util. concurrent 并发类代替自己手写锁。
- 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
- 尽量减少同步的代码块。

### 9.什么是ThreadLocal?

​        ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。

​        ThreadLocal 的经典使用场景是数据库连接和 session 管理等。

### 10.synchronized 、volatile、Lock、ReentrantLock的区别

> synchronized

​        **synchronized:**底层是使用操作系统的mutex lock实现的。具有内存可见性和操作原子性。

- **内存可见性:**指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
- **操作原子性:**持有同一个锁的两个同步块只能串行地进入
- **有序性:**禁止指令重排

​            synchronized用的锁是存在Java对象头里的。

​                    JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。代码块同步是使用monitorenter和monitorexit指令实现的,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。

​                    根据虚拟机规范的要求,在执行monitorenter指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1;相应地,在执行monitorexit指令时会将锁计数器减1,当计数器被减到0时,锁就释放了。如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

注意两点:

​        1、synchronized同步快对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;

​        2、同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。

> volatile   [ˈvɑːlətl]

- volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
- volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
- volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

https://blog.csdn.net/u012723673/article/details/80682208

https://blog.csdn.net/j550341130/article/details/79930690

https://www.jianshu.com/p/c9c77d771221

volatile是Java提供的一种轻量级的同步机制

​        **为什么不能保证原子性?**

~~~
(1)当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
(2)这个写会操作会导致其他线程中的volatile变量缓存无效。
~~~

​        **什么是变量可见性?**

         在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改volatile变量后他会立即被更新到主内存中,其他线程读取volatile变量时,会直接从主内存中读取。

​        **volatile禁止指令重排**

![image-20210621171614995](../../../OtherSoftware/Typora/picture/image-20210621171614995.png)

​        CPU会重排、JIT代码生成器中解释器会重排指令。解释器是将字节码解释为机器语言;JIT是为了让重复执行的代码(热点代码)避免重复解释,将之编译为本地机器码,用到时直接执行机器码,达到了节约时间的目的。

​    CPU1                                  CPU2

store buffer                      store buffer        加入内存屏障告诉CPU在进行下一步之前保证Store Buffer已刷新进cache

cache1                                    cache2

invalidate queue        invalidate queue    加入内存屏障告诉CPU在进行下一步之前保证Invalidate Queue里的消息已经执行

​                            bus

​                            内存

​            重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。指令队列在CPU执行时不是串行的,当某条指令执行时消耗较多时间时,CPU资源足够时并不会在此无意义的等待,而是开启下一个指令。开启下一条指令是有条件的,即上一条指令和下一条指令不存在相关性。

用volatile修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

**volatile使用场景**

​    我们可以通过volatile禁止指令重排达到有序性。典型用处是在单例双重检查锁 DCL(double-checked locking)

```java
static volatile CheckManager instance;
public static CheckManager getInstance() {
    if (instance == null) {
        synchronized (CheckManager.class) {
            if (instance == null) {
                instance = new CheckManager();        //如果不用volatile修饰这行会出错
            }
        }
    }

    return instance;
}

在new对象时会有三步操作
    1.分配内存
    2.初始化对象
    3.设置instance指向刚分配的地址
不加volatile  多线程只为执行1,3  没有执行2
    由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值