Bootstrap

Java基础

Java基础

1、我们为什么学Java

1.1、Java是什么?

Java 旨在竭尽所能为最广泛的计算平台开发可移植的高性能应用程序。

通过使应用程序在异构环境之间可用,企业可以提供更多的服务,提高最终用户生产力并加强沟通与协作,从而显著降低企业和消费类应用程序的拥有成本。
在这里插入图片描述
Java 是开发人员的无价之宝,使他们可以:

  • 在一个平台上编写软件,然后即可在几乎所有其他平台上运行
  • 创建可在 Web 浏览器中运行并可访问可用 Web 服务的程序
  • 开发适用于在线论坛、存储、投票、HTML 格式处理以及其他用途的服务器端应用程序
  • 将采用 Java 语言的应用程序或服务组合在一起,构成高度定制的应用程序或服务
  • 为移动电话、远程处理器、微控制器、无线模块、传感器、网关、消费产品及几乎其他任何电子设备编写强大而高效的应用程序
1.2、Java主要特性
简单性
面对对象
可移植性
高性能
分布式
多态性
多线程
安全性
健壮性
  • Java 语言是简单的:
    -Java 语言的语法与 C 语言和 C++ 语言很接近,使得大多数程序员很容易学习和使用。另一方面,Java 丢弃了 C++ 中很少使用的、很难理解的、令人迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地,Java 语言不使用指针,而是引用。并提供了自动分配和回收内存空间,使得程序员不必为内存管理而担忧。

  • Java 语言是面向对象的:
    Java 语言提供类、接口和继承等面向对象的特性,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为 implements)。Java 语言全面支持动态绑定,而 C++语言只对虚函数使用动态绑定。总之,Java语言是一个纯的面向对象程序设计语言。

  • Java语言是分布式的:
    Java 语言支持 Internet 应用的开发,在基本的 Java 应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括 URL、URLConnection、Socket、ServerSocket 等。Java 的 RMI(远程方法激活)机制也是开发分布式应用的重要手段。

  • Java 语言是健壮的:
    Java 的强类型机制、异常处理、垃圾的自动收集等是 Java 程序健壮性的重要保证。对指针的丢弃是 Java 的明智选择。Java 的安全检查机制使得 Java 更具健壮性。

  • Java语言是安全的:
    Java通常被用在网络环境中,为此,Java 提供了一个安全机制以防恶意代码的攻击。除了Java 语言具有的许多安全特性以外,Java 对通过网络下载的类具有一个安全防范机制(类 ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查,并提供安全管理机制(类 SecurityManager)让 Java 应用设置安全哨兵。

  • Java 语言是体系结构中立的:
    Java 程序(后缀为 java 的文件)在 Java 平台上被编译为体系结构中立的字节码格式(后缀为 class 的文件),然后可以在实现这个 Java 平台的任何系统中运行。这种途径适合于异构的网络环境和软件的分发。

  • Java 语言是可移植的:
    这种可移植性来源于体系结构中立性,另外,Java 还严格规定了各个基本数据类型的长度。Java 系统本身也具有很强的可移植性,Java 编译器是用 Java 实现的,Java 的运行环境是用 ANSI C 实现的。

  • Java 语言是解释型的:
    如前所述,Java 程序在 Java 平台上被编译为字节码格式,然后可以在实现这个 Java 平台的任何系统中运行。在运行时,Java 平台中的 Java 解释器对这些字节码进行解释执行,执行过程中需要的类在联接阶段被载入到运行环境中。

  • Java 是高性能的:
    与那些解释型的高级脚本语言相比,Java 的确是高性能的。事实上,Java 的运行速度随着 JIT(Just-In-Time)编译器技术的发展越来越接近于 C++。

  • Java 语言是多线程的:
    在 Java 语言中,线程是一种特殊的对象,它必须由 Thread 类或其子(孙)类来创建。通常有两种方法来创建线程:其一,使用型构为 Thread(Runnable) 的构造子类将一个实现了 Runnable 接口的对象包装成一个线程,其二,从 Thread 类派生出子类并重写 run 方法,使用该子类创建的对象即为线程。值得注意的是 Thread 类已经实现了 Runnable 接口,因此,任何一个线程均有它的 run 方法,而 run 方法中包含了线程所要运行的代码。线程的活动由一组方法来控制。Java 语言支持多个线程的同时执行,并提供多线程之间的同步机制(关键字为 synchronized)。

  • Java 语言是动态的:
    Java 语言的设计目标之一是适应于动态变化的环境。Java 程序需要的类能够动态地被载入到运行环境,也可以通过网络来载入所需要的类。这也有利于软件的升级。另外,Java 中的类有一个运行时刻的表示,能进行运行时刻的类型检查。

2、关于Java那些事

宣言:一次编写,到处运行

  • Write Once,Run Anywhere

Java三大版本

JavaSE: 标准版 (桌面程序,控制台开发…)
    
JavaME: 嵌入式开发 (手机,小家电…),已经凉了
    
JavaEE: 企业级开发 (Web端,服务端开发…)JavaSE为基础

JDK JRE JVM

JDKJava Development Kit (Java开发者工具,包括 JREJVM)
    
JREJava Runtime Environment (Java运行时环境)
    
JVMJava Virtual Machine (Java虚拟机,跨平台核心)
    

JDK JRE JVM之间的逻辑关系
在这里插入图片描述

安装开发环境

## 卸载JDk
   删除Java安装目录
   删除环境变量JAVA_HOME
   删除path下关于JAVA的目录
   Java -version
    
## 安装JDK
   浏览器搜索JDK8,下载电脑对应的版本,
   		如64位操作系统下载 jdk-8u281-windows-x64.exe
   双击安装JDK
   配置环境变量
   		我的电脑-》属性-》系统高级设置-》环境变量
   		系统变量 新建–> JAVA_HOME 输入对应的jdk安装路径
   		path变量–>% JAVA_HOME%\bin
   测试是否成功 cmd–>Java -version

Java注释:

1.单行注释:用于注释一行内容
快捷键:CTRL+/IDEA 环境,下同)

2.多行注释:用于一次性给多行代码注释,也可以使用单行注释只不过要选中多行

快捷键:CTRL+shift+/

3.文本注释

快捷键:/**+回车

3、Java基础

3.1、标识符和关键字
  • Java 所有的组成部分都需要名字。类名、变量名、方法名都被称为标识符
    关键字

  • 标识符注意点

    • 所有标识符都应该以 字母、$(美元符)、_(下划线) 开头
    • 首字母之后可以是 字母、$、_ 或数字任何字符组合
    • 关键字不能作为变量名或方法名
    • 标识符大小写敏感
    • 可以用中文命名,但不建议使用
    • 当然 ,如果可以,请放弃拼音式命名
3.2、数据类型
  • 强类型语言

​ 要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用

  • 弱类型语言:JavaScript,Python

    Java的数据类型分为两大类

3.2.1基本类型(primitive type)
//整数
int num1 = 10; //最常用,只要别超过21亿(2^31-1)
byte num2 = 20; //-128~127
short num3 = 30;
long num4 = 30L; //long类型数字后面要加个L

//小数:浮点数
float num5 = 50.1F; //float类型数字后面要加个F
double num6 = 3.141592653589793238;
//特殊类型(大十进制数:)
	1.public BigDecimal(double val)double表示形式转换为BigDecimal *不建议使用

  2.public BigDecimal(int val)  将int表示形式转换成BigDecimal

  3.public BigDecimal(String val)  将String表示形式转换成BigDecimal
//字符
char name = '国';
//字符串, String不是关键字,是类
//String namea = "薛之谦";

//布尔值:是非
boolean flag = true

float double BigDecimal 之间的区别

  • double是双精度浮点数,内存占8个字节,有效数字16位,范围从10-308到10308 和 -10-308到-10-308,有效小数位15位
  • float是单精度浮点数,内存占4个字节,有效数字8位,范围从10-38到1038 和 -1038到-10-38,有效小数位6-7位
  • CPU处理float的速度比处理double快。double的精度高,double消耗内存是float的两倍。
  • 小数默认是double类型,用float时需要进行强转,或者在小数后加上f。
    在这里插入图片描述
    输出:
    在这里插入图片描述
3.3、什么是字节
  • 位(bit):是计算机内部数据存储的最小单位,11001100是一个八位二进制数

  • 字节(byte, B):是计算机中 数据处理 的基本单位,习惯上用大写B表示
    1B(byte) = 8bit,1字节等于8位

  • 字符:指计算机中使用的字母,数字,字和符号

1bit表示11Byte表示一个字节 1B=8b
1024B = 1KB, 1024KB = 1M, 1024M = 1G
3.4、类型转换
  • 由于Java是强类型语言,所以不同类型的数据先转化位同一类型,再进行运算。

    • 强制转换,(类型)变量名,容量由高到低

    • 自动转换,容量由低到高

注意点:


   1.不能对布尔值进行转换
   2.不能把对象类型转换为不相干的类型
   3.在把高容器转换到低容量的时候,强制转换
   4.可能存在内存溢出,或者精度问题
3.5、变量、常量、作用域、命名规范

1. 变量

  • Java是一种强类型语言,每个变量使用前都必须声明其类型
  • Java变量是程序中最基本的存储单元,要素包括变量名,变量类型和作用域
//数据类型 变量名 = 值;
//可以在同一行使用逗号隔开同多个类型的变量,但是不建议这样使用
  1. 变量作用域
- 类变量(static):伴随着类的加载而产生,其所有权属于该类
- 实例变量 		 :伴随着类的加载而产生,其所有权属于该类
- 局部变量  		 :伴随着类的加载而产生,其所有权属于该类
  1. 变量的命名规范:
    在这里插入图片描述

引用于《阿里巴巴Java开发手册》

  1. 常量
  • 常量:
    其值经初始化后,在程序运行过程不允许被更改。
//修饰符 不存在先后顺序,static可以写final后面 常量一般用大写字符

在这里插入图片描述

3.6、运算符
3.6.1、自增自减运算符
int a = 3;
int b = a++; //b=a,a=a+1 先赋值再自增 即b=a   a=a+1
int c = ++a; //a=a+1,c=a 先自增再赋值 即a=a+1  c=a

在这里插入图片描述
输出:
在这里插入图片描述

3.6.2、幂运算 2^3 222=8
double pow = Math.pow(2,3); // (底数,指数)double型
System.out.println(pow); //8.0
3.6.3、逻辑运算符
第一个值为false,后面就不进行判定了
 && 逻辑与运算:两个变量都为真,结果为true
第一个值为true,后面就不进行判定了
 || 逻辑与运算:两个变量有一个为真,结果为true
 ! 取反,真变为假,假变为真
3.6.4、位运算

在这里插入图片描述
只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。

因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。

3.6.5、三元运算符
 x ? y : z
//如果x为真,则结果为y,否则为z

在这里插入图片描述
输出:
在这里插入图片描述

3.7、包机制
3.7.1、包的语法格式:
  • package Base_Java;
  • 一般利用公司域名倒置作为包名;com.example.www
import java.util.*; //通配符* 导入包下所有的类
3.8、JavaDoc生成文档
  • javadoc命令是用来生成自己API文档的

  • 参数信息
    @author 作者名
@version 版本号
@since 指明最早用的jdk版本
@param 参数名
@return 返回值情况
@throws 异常抛出情况

    API文档:

  • 打开某个类所在文件夹下的cmd命令行

输入:javadoc -encoding UTF-8 -charset UTF-8 Doc(类名).java
会自动生成该类有关的API文档,查看文件夹发现多了一些文件
打开 index.html(首页)查看文档注释
3.9、用户输入Scanner && Random 随机生成字母

通过Scanner类可以获取用户的输入:

Scanner scanner = new Scanner(System.in);
		while (scanner.hasNext()){
			System.out.println("scanner.nextInt() = " + scanner.nextInt());
		}
  • 通过Scanner类的 next()与 nextLine()方法获取用户的字符串,
  • 一般用hasNext()与hasNextLine()判断是否还有输入的数据。

Random 随机生成字母:
生成随机大写字母

Random random = new Random();
		char c = (char) (random.nextInt(26) + 65);
		System.out.println("c = " + c);

3.10、流程控制方法

Java的基本结构就是顺序结构,除非特别指明,否则就按语句一条一条执行。
- 顺序结构

- if单选择结构 if( )
- if双选择结构 if( ){ }else{ }
- if多选择结构 if( ){ }else if{ }else{}
- 嵌套的if结构 if( ){ if( ) }

- switch多选择结构
注意:再JDK新特性中已经出现了新写法

char grade = 'C'; //JDK新特性 可以是字符串(字符本质还是数字)
switch (grade){
    case 'A':
        System.out.println("优秀");
        break; //可选,跳出当前结构
    case 'B':
        System.out.println("良好");
        break;
    case 'C':
        System.out.println("合格");
        break;
    default: //默认,以上值没匹配到
        System.out.println("不及格");
        break;
}
  • 循环结构
    • while循环
//计算1+2+3+...+100
int i=0;
int sum=0;
while(i<100){
    i++;
    sum+=i;
}
System.out.println(sum); //5050
    • do…while循环
//先执行后判断,至少执行一次
do{
    i++;
    sum+=i;
}while(i<100) //跟上面效果一样
    • for循环
//(初始化;条件判断;迭代)
for(int i=0;i<100;i++){
    i++;
    sum+=i;
}
    • foreach遍历
int [] numbers = {10,20,30,40,50}; //定义一个数组
for (int x:numbers){
    System.out.println(x); //遍历数组的元素 10 20 30 40 50
}

- - break & continue

  • break 强行退出循环,不进行下一次循环条件判断。
  • continue 终止本次循环过程,跳过剩余语句,之后进行下一次循环条件判断。
3.11、Java方法
3.11.1、方法的定义

Java的方法类似与其他语言的函数,是一段用来完成特定功能的代码片段。
方法包含一个方法头和一个方法体。

  • 修饰符:定义了方法的访问类型
  • 返回值类型:方法可能会返回值。returnValueType是方法返回值的数据类型。有些方法没有返回值,则returnValueType为关键字void。
  • 方法名:是方法的实际名称
  • 参数类型:参数列表是指方法的参数类型、顺序和参数个数。方法可以不包含任何参数。
  • 形参:在方法被调用时用于接收外界输入的数据。
  • 实参:调用方法时实际传给方法的数据。
  • 方法体:方法体包含具体的语句,定义该方法的功能。
修饰符 返回值类型 方法名(参数类型 参数名,...{
   方法体...
   return 返回值;
}
3.11.1、方法的调用

调用方法:对象名.方法名(实参列表)。

  • 当方法返回一个值的时候,方法调用通常被当成一个值。
int larger = max(30,40);



  • 如果方法返回值是void,方法调用一定是一条语句。
    在这里插入图片描述

扩展:值传递和引用传递 ( Java都是值传递)

3.11.1、方法的重载

重载是在一个类中,有相同的方法名,参数列表不同的方法。

方法重载的规则:

  • 方法名称必须相同
  • 参数列表必须不同(个数、参数类型、或排序不同)
  • 返回类型可以相同也可以不相同
  • 仅仅返回类型不同不足以成为方法的重载
3.11.1、递归
  • 递归就是:A方法调用A方法,自己调用自己!

  • 递归的能力在于用有限的语句来定义对象的无限集合。

阶乘 n!


public static int f(int n){
    if(n==1) return 1;
    return n*f(n-1); //递归:调用自身
}
3.12、Java数组
3.12.1、数组的定义
  • 数组是相同类型数据的有序集合
  • 数组描述的是相同类型的若干数据,按照一定先后次序排序组合而成
  • 其中,每一个数据称作一个数组元素,每个数组元素可以通过下标访问它们
3.12.2、数组的声明创建
  • 首先必须声明数组变量,才能在程序中使用数组。
dataType[] arrayRefVar = new dataType[arraySize]; //int[] nums=new int[10]
  • 数组的元素是通过索引访问的,数组索引从0开始
获取数组长度:arrays.length
int[] nums; //1.声明一个数组
nums = new int[3]; //2.创建一个数组
//3.给数组元素赋值
nums[0]=1;
nums[1]=2;
nums[2]=3;
for (int num : nums) { //打印数组所有元素
    System.out.println(num);
}
3.12.3、数组的三种初始化
  • 静态初始化
//静态初始化:创建+赋值
int[] a={1,2,3};
Man[] mans={new Man(1,1),new Man(2,2)}
  • 动态初始化
//包含默认初始化
int[] a=new int[2]; //默认值为0
a[0]=1;
a[1]=2;
  • 默认初始化
数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。
3.12.4、数组的基本特点
  • 其长度是确定的,数组一旦被创建,它的大小就是不可改变的。

  • 其元素必须是相同类型,不允许出现混合类型。

  • 数组中的元素可以是任何数据类型,包括基本类型和引用类型。

  • 数组变量属于引用类型,数组也可以看作对象,其中每个元素相当于该对象的成员变量。

  • 数组本身就是对象,Java中对象是在堆中的,因此数组本身是在堆中的。

3.12.5、数组的使用
  • For-Each循环遍历
int[] arrays = {1,2,3,4,5};
for (int array : arrays) {
    System.out.println(array);
}
  • 数组作方法入参
//打印数组元素
    for (int i = 0; i < a.length; i++) {
        System.out.print(a[i]+" ");
    }
  • 数组作返回值
//反转数组

    int[] result = new int[arrays.length];
    //反转的操作
    for (int i = 0; i < arrays.length; i++) {
        result[i] = arrays[arrays.length-i-1];
    }
    return result;

  • 多维数组
    多维数组可以看成数组的数组,比如二维数组就是一个特殊的数组,其每一个元素都是一个一维数组。
3.12.6、Arrays类
  1. 数组的工具类java.util.Arrays

    由于数组对象本身并没有什么方法可以供我们使用,但API提供了一个工具类Arrays供我们使用。
    Array类中的方法都是static修饰的静态方法,使用时直接使用类名进行调用,可以不用对象调用。

  2. 常用功能

    给数组赋值:fill方法。
    排序:sort方法,升序。
    比较数组:equals方法比较数组中元素值是否相等。
    查找数组元素:binarySearch对排序好的数组进行二分查找法操作。

  3. 冒泡排序

//冒泡排序
//1.比较数组中两个相邻的元素,如果第一个数大于第二个数,交换它们位置
//2.每一次比较,都会产生一个最大或最小的数字(升序为最大数)
//3.下一轮则可以少一次排序
//4.依次循环,直到结束

4.稀疏数组

1.有效值的个数

2.创建一个稀疏数组

3.遍历二维数组,将有效值存放到稀疏数组

4.输出稀疏数组

4.面向对象

4.1、面向对象
  • 面向对象思想

    • 对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象来分析整个系统。
4.2、面向对象编程

(Object-Oriented Programming, OOP)本质:以类的方式组织代码,以对象的组织(封装)数据。

  • 对象是类的实例化,类是对象的抽象。
  • 类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但并不能代表某一个具体的事物。
    动物、植物、手机、电脑…
    Person类、Pet类、Cat类等,都是用来描述/定义某一具体的事物应该具备的特点和行为。
  • 对象是抽象概念的具体实例,如张三是人的一个具体实例、张三家里的狗旺财就是狗的一个具体实例。
    创建与初始化对象
  • 使用new关键字创建对象的时候,除了分配内存之外,还会给创建好的对象进行默认的初始化,以及对类中构造器的调用。
  • 类中的构造器也被称为构造方法,创建对象时必须要调用。有以下特点:
    • 必须和类的名字相同
    • 没有返回类型,也不能写void
    • 一个类即使什么都不写,也会存在一个默认的构造方法
4.3、三大特性
  • 封装
  • 继承
  • 多态
4.4、super & this
  • super()调用父类的构造方法,必须在构造方法的第一个
  • super必须只能出现在子类的方法或构造方法中
  • **super()和this()**不能同时调用构造方法,因为this也必须写在第一行
  • super与this的区别:super代表父类对象的引用,只能在继承条件下使用;this调用自身对象,没有继承也可以使用。
  • super(); //隐藏代码,默认调用了父类的无参构造,要写只能写第一行
4.5、面向对象进阶
4.5.1、方法重写(0verriding)

在Java中覆盖继承父类的方法就是通过方法的重写来实现的。所谓方法的重写是指子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。

4.5.2、重写规则

在重写方法时,需要遵循以下的规则:

  • 父类方法的参数列表必须完全与被子类重写的方法的参数列表相同,否则不能称其为重写而是重载。
  • 父类的返回类型必须与 被子类重写的方法返回类型相同,否则不能称其为重写而是重载。
  • Java中规定,被子类重写的方法不能拥有比父类方法更加严格的访问权限。编写过Java程序的人就知道,
  • 父类中的方法并不是在任何情况下都可以重写的,当父类中方法的访问权限修饰符为private时,该方法只能被自己的类访问,

不能被外部的类访问,在子类是不能被重写的。如果定义父类的方法为public,在子类定义为private,程序运行时就会报错。

4.5.3、方法重载(Overloading)
  • 方法重载是让类调用方法时通过传递给它们的不同个数和类型的参数来决定具体使用哪个方法,这就是多态性。

  • 方法的重载在实际应用中也会经常用到。不仅是一般的方法,构造方法也可以重载。

  • 因此,每个重载方法的参数的类型或个数必须是不同。

  • 当Java调用一个重载方法是,参数与调用参数匹配的方法被执行。在使用重载要注意以下的几点:

    • 在使用重载时只能通过不同的参数列表,必须具有不同的参数列表。
    • 不能通过访问权限、返回类型、抛出的异常进行重载。
    • 方法的异常类型和数目不会对重载造成影响。
    • 可以有不同的返回类型,只要参数列表不同就可以了。
    • 可以有不同的访问修饰符。
    • 可以抛出不同的异常。
4.5.4、方法重写与方法重载的区别

注意事项:
java中如何理解overload , override 和 overwrite

override:覆写是对接口方法名实现
overload:重载 同一个类里同一个方法名,有不同入参
overwrite:重写 重写父类所属方法

4.5.5、重载规则
  • 方法名、参数列表必须相同
  • 修饰符范围可以扩大,不能缩小(public>protect>private)
  • 抛出的异常 范围可以被缩小,不能扩大
  • 被**static(属于类,不属于实例),final(常量方法),private(私有)**修饰的方法不能重写
  • 动态编译:类型
4.5.6、instanceof和类型转换
  • instanceof 引用类型比较,判断一个对象是什么类型

  • 类型转换

  • 父类引用指向子类的对象

4.5.7、Static
  • 静态变量可以直接用类名访问,也称类变量。

  • 静态变量(或方法)对于类,所有对象(实例)所共享。

  • 静态区代码 加载类时一起被初始化,最早执行且只执行一次(第一次new)。

  • Math->随机数:

4.5.8、抽象类(abstract)
  • abstract修饰的类就是抽象类,修饰的方法就是抽象方法。

  • 抽象类中可以没有抽象方法,但有抽象方法的类一定要声明为抽象类。

  • 抽象类不能使用new来创建对象,它是用来让子类继承的。

  • 抽象方法只有方法的声明,没有实现,让其子类实现。

  • 子类继承抽象类,必须实现抽象类的所有方法,否则该子类也要声明为抽象类。

  • abstract 抽象类 类只能单继承(接口可以多继承)

4.5.9、接口(interface)

普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有
接口:只有规范,没有方法实现,专业的约束!约束与实现分离:面向接口编程~

注意点

  • 接口没有构造方法,不能被实例化
  • 实现类必须要重写接口中的方法
  • 实现类(implements) 可以实现多个接口

JDK8接口新增了以下方法
在这里插入图片描述
接口继承注意事项
在这里插入图片描述

4.5.10、内部类

内部类就是在一个类的内部再定义一个类,比如A类中定义了一个B类,那么B就是A的内部类,而A相对B来说就是外部类

  • 成员内部类:可以操作外部类的私有属性及方法

    • 在这里插入图片描述
      在这里插入图片描述
  • 静态内部类:static修饰,不能访问外部类私有属性

    • 在这里插入图片描述
  • 局部内部类:外部类的方法里定义的类

    • 在这里插入图片描述
  • 匿名内部类:没有名字初始化类

在这里插入图片描述

4.5.11、 final

在这里插入图片描述

4.5.12、 枚举

在这里插入图片描述

4.5.13、 泛型

在这里插入图片描述
在这里插入图片描述
注意事项:
泛型擦除:泛型是工作在编译阶段的,一旦编译完成为class文件,就不存在泛型了,这就是泛型擦除;
泛型不支持基本数据类型,仅支持对象类型(引用数据类型);

5.异常

  • 异常英文(Exception),意思是例外,这些例外情况需要我们写程序做出合理的处理,而不至于让程序崩溃。

  • 异常指程序运行中出现的不期而至的各种状况:文件找不到,网络连接错误,非法参数等。

  • 异常发生在程序运行期间,它影响了正常的执行流程。

5.1简单分类

在这里插入图片描述

  • 检查型异常:最具代表性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如用户要打开一个不存在的文件时引发的异常,这些异常在编译时不能被简单地忽略。
  • 运行时异常:是可能被程序员避免的异常,与检查性异常相反,运行时异常可以在编译时忽略。(通俗的理解,运行时异常属于那种系统认为程序员本不会出现的错误,所以在编译时不予以提示,但是这一个错误在程序运行时又确确实实被系统检测到了,系统还是把找一个错误以异常的形式抛出来)
  • 错误Error:错误不是异常,而是脱离程序员控制的问题。错误在代码经常被忽略。例如当栈溢出,一个异常就发生了,它们在编译也检查不到。

需要注意的事:
在Exception 中除了RuntimeException 及其子类 如(数组下标越界等等) 均为编译时异常(在编译过程中就会给出异常提示的,如日期解析异常);

5.2、异常处理机制
  • 抛出异常
  • 捕获异常
  • 异常处理关键字:try、catch、finally、throw、throws

自定义异常

在这里插入图片描述开发中常见的异常处理方式:
在这里插入图片描述

6.常用API

6.1 、date

在这里插入图片描述
在这里插入图片描述

新的日期API:
在这里插入图片描述
但需要注意的是:
在这里插入图片描述

新旧日期API:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
注意事项:
在这里插入图片描述

6.2 、math
int abs = Math.abs(-5);
Math.sqrt(1000.0);
Math.max(1000,2000);
Math.min(1000,2000);
Math.round(1000.6f);  //四舍五入
ceil()  // 向上取整
floor() // 向下取整
Math.random();
6.3、BigDecimal类

在这里插入图片描述

在这里插入图片描述

6.4、类的克隆技术

类要实现 浅克隆,需要做两步:

  1. 实现Cloneable接口;
  2. 重写Object的clone方法。
 public static void main(String[] args) {
  
        a a1 = new a();
        Object clone = a1.clone();


    }

    class a implements Cloneable {

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

在这里插入图片描述

深克隆
在这里插入图片描述

6.5、 Timer定时器
6.6、Calendar
6.7、String

String:字符串,使用一对“”引起来表示

  • String声明为final的,不可被继承

  • String实现了Serializable接口:表示字符串是支持序列化的

  • 实现了comparable接口:表示String可以比较大小

  • String内部定义了final char【】 value用于存储字符串数据

  • String:代表不可变的字符序列:简称:不可变性。

体现:当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值

  • 通过字面量的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中,字符串常量池中是不会存储相同内容的字符串。
6.7.1基本类型与字符串之间的转换
6.8、String 、StringBuilder 、StringBuffer

在这里插入图片描述
在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/40e849d734594338ad1603ec9bbf39a5.png

StringJoiner
在这里插入图片描述

6.8.1、差别与联系

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.8.2、 相关Api

在这里插入图片描述

在这里插入图片描述

6.9、Arrays 和自定义排序Comparable

Arrays.sorts()
在这里插入图片描述

在这里插入图片描述

6.10、方法引用、特定类型方法引用、特定类型方法引用(JDK8 )

7.集合

在这里插入图片描述
在这里插入图片描述
collection框架有自己的接口和实现,主要分为**Set接口,List接口和Queue接口。**它们有各自的特点,

  • Set的集合里不允许对象有重复的值
  • List允许有重复,它对集合中的对象进行索引
  • Queue的工作原理是FCFS算法(First Come, First Serve)
    在这里插入图片描述
    遍历Colloction 元素: 迭代器 、增强for循环、lambda 表达式
// 迭代器  iterator 
Collection<Integer> integers = new ArrayList<>();
        while(integers.iterator().hasNext()){
            System.out.println("integers = " + integers.iterator().next());
        }

// 增强for循环  for
Collection<Integer> integers = new ArrayList<>();
        
        for(int e:integers){
            System.out.println("e = " + e);
        }
// lambda 表达式
 Collection<Integer> integers = new ArrayList<>();
        
        
        integers.forEach(integer -> System.out.println("integer = " + integer));
7.1、HashMap的工作原理
  • HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。

  • 当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。

  • 当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。

  • HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。

  • HashMap在每个链表节点中储存键值对对象。

7.2、什么是HashMap

在这里插入图片描述

  • HashMap实现了Map接口,Map接口对键值对进行映射。

  • Map接口有两个基本的实现,HashMap和TreeMap。

  • TreeMap保存了对象的排列次序,而HashMap则不能。

  • HashMap允许键和值为null。

  • Map中不允许重复的键。所以只允许最多一个空键存在

  • HashMap是非synchronized的,但collection框架提供方法能保证HashMap synchronized,这样多个线程同时访问HashMap时,能保证只有一个线程更改Map。

public Object put(Object Key,Object value)方法用来将元素添加到map中。
7.3、HashMap同步?

HashMap可以通过下面的语句进行同步:

 Map m = Collections.synchronizeMap(hashMap);
7.4、什么是HashSet
  • HashSet实现了Set接口,它不允许集合中有重复的值,

  • 当我们提到HashSet时,第一件事情就是在将对象存储在HashSet之前,要先确保对象重写equals()和hashCode()方法

这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。

如果我们没有重写这两个方法,将会使用这个方法的默认实现。

public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会立即返回false,如果成功添加的话会返回true
7.5HashMap和HashSet的区别
HashMapHashSet
HashMap实现了Map接口HashSet实现了Set接口
HashMap储存键值对HashSet仅仅存储对象
使用put()方法将元素放入map中使用add()方法将元素放入set中
HashMap中使用键对象来计算hashcode值HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false
HashMap比较快,因为是使用唯一的键来获取对象HashSet较HashMap来说比较慢
7.6、HashMap和Hashtable的区别

HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。

Hashtable和HashMap 小结

  • Hashtable和HashMap有几个主要的不同:线程安全以及速度。

  • 在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java5或以上的话,请使用ConcurrentHashMap吧。

7.7、ArrayList

ArrayList 的工作原理
基于数组构成
在这里插入图片描述

**需要注意的是:**由于ArrayList的特殊工作原理,决定了他的对于数据量特别大的情景或者需要频繁的增删数据时候他的工作效率是特别低的,但是在数据量相对较小的时候或者根据随机索引查询数据时的效率较高,这一特性和顺序表的原理类似。

求 101 至 200 之间的所有素数
(素数:只能被1和自己整除的数字)

ArrayList<Integer> res = new ArrayList<Integer>();
        int sqrtNum = (int) (Math.sqrt(200));
        for (int j = 101; j <= 200; j++) {
            int count = 0;
            for (int i = 1; i <= sqrtNum; i++) {
                if (j % i == 0) {
                    count++;
                }
            }
            if (count == 1) {
                res.add(j);
            }

        }
        System.out.println("res = " + res);
        System.out.println("该区域间共有素数 = " + res.size() + "个");
7.8、LinkedList

LinkedList 的工作原理
基于双向链表实现的
故而其对于首尾元素的crud 操作是极快的,但对于其他元素而言,增删元素较快,查询较慢,这也符合双向链表的特性

7.9、Set

特点:无序、无索引、不重复

在这里插入图片描述

HashSet 底层原理
前置内容:哈希表 以及 哈希冲突
在这里插入图片描述
横向是数组,纵向是链表

需要注意的是,在这一方案中,如果数组快占满了:
数组已经使用空间 / 数组长度 ==默认加载因子的时候 ,则会进行数组扩容操作

或者链表过长则他的查询效率会下降
在这里插入图片描述
前置内容:数据结构——树
在这里插入图片描述

但是二叉排序树可能会出现
左下角的极端情况,于是就有了平衡二叉树,使得树尽可能矮小在这里插入图片描述
红黑树则是可以自平衡的二叉树
在这里插入图片描述
HashSet 集合去重复在这里插入图片描述

LinkedHashSet && TreeSet的工作原理
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
部分小结:
在这里插入图片描述

7.10、Collection工具类

可变参数
在这里插入图片描述
Collection工具类的常见静态方法:
在这里插入图片描述

8. lambda表达式

定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口

  • lambda表达式写法
package com.lmr.practice;

public class TestLambda2 {

        public static void main(String[] args) {
            ILove  love = null;
          //1.lambda表示简化
          love = (int a)->{
                 System.out.println("LOvee"+ a);
             };
          
          //简化1:参数类型
            love = (a)->{
                System.out.println("LOvee"+ a);
            };
            
            //简化2:简化括号
            love = a->{
                System.out.println("LOvee"+ a);
            };
            
            //简化3:简化花括号
            love = a-> System.out.println("LOvee"+ a);
            
             love.love(205);
        }

    }
//函数式接口
interface ILove{
    void love(int a);
}

9.Stream 流

感兴趣可以了解,有需求时可以再学习

List<String> sites = new ArrayList<String>();
        sites.add("Google");
        sites.add("Runoob");
        sites.add("Taobao");
        sites.add("Weibo");
        Set<String> sites1 = sites.stream().filter(site -> site.startsWith("G") || site.endsWith("o")).collect(Collectors.toSet());

        System.out.println("site1 = " + sites1);

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10、IO 流

创建File对象:
在这里插入图片描述
File判断文件是否存在,获取文件信息:
在这里插入图片描述
递归问题:猴子🐵吃桃

public static void main(String[] args) {

        System.out.println(test(1));
    }

    static int test(int day) {
        return day==10?1:(2*test(day+1)+1)+1;

    }

文件搜索:

public static void main(String[] args) {

        test("DemoApplication.java",new File("D:\\"));
    }

    static int test(String targetFile , File filePath) {
        if(filePath!=null&& !filePath.isDirectory()){
            return 0;
        }
        File[] files = filePath.listFiles();
        if(files!=null&&files.length>0){
            for (File file : files) {
                if(file.isDirectory()){
                    test(targetFile,file);
                }else {
                    if (file.getName().contains(targetFile)){
                        System.out.println("file = " + file.getAbsolutePath());
                    }
                }
            }
        }
10.1、字符集

在这里插入图片描述
在这里插入图片描述

10.2、IO流的分类

在这里插入图片描述FileInputStream:
在这里插入图片描述
FileOutStream:
在这里插入图片描述
释放资源 try-catch-finally | try -catch-resource

finally包裹的代码块最终都会执行,除非虚拟机JVM挂了,所以经常用来释放资源的操作

                    System.exit(0);

字节流小结:
字节流因为容易截断字节,所以常常不用来读写文本文件,而是用来复制文件

FileReader:
在这里插入图片描述
FileWriter:
在这里插入图片描述
需要注意的是:字符输出流写出的数据只有关闭(close())或者刷新(flush())流资源才会生效

缓冲流
在这里插入图片描述

10.2、IO 框架

封装了Java对文件,数据进行操作的代码,提供着更简单的方式进行文件操作,数据读写
在这里插入图片描述

10.3、其他流的实现

11.网络编程

InetAddress  	 获取地址
InetAddress.getCanonicalHostName		规范的名字
InetAddress.getHostAddress		IP
InetAddress.getHostName		域名或自己电脑的名字
InetSocketAddress		实现 IP 地址及端口
InetAddress				实现 IP 地址
ServerSocket				建立服务的端口
.accept						阻塞监听等待连接
Socket						创建连接
.getInputStream				获取IO输入流
.getOutputStream			获取IO输出流
ByteArrayOutputStream		byte类型数组管道输出流
FileOutputStream		文件字符输出流
FileInputStream			文件字符输入流
shutdownOutput			停止输出
DatagramSocket			数据包端口
DatagramPacket			数据包
.send			发送
.receive		阻塞接收
BufferedReader			缓存区读取
InputStreamReader		输入流读取
.readLine				读取的一行内容
URL			统一资源定位符
.openConnection				打开连接
HttpURLConnection			指定协议HTTP
11.1、概述
  • 计算机网络

计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统

11.2、网络通信的要素

如何实现网络的通信?

通信双方地址:

  • ip
  • 端口号
  • 192.168.16,124:5900

规则:网络通信的协议

http, ftp, smtp, tcp,udp…

TCP/IP参考模型:

小结

1.网络编程中有两个主要的问题

  • 如何准确的定位到网络上的一台或者多台主机

  • 找到主机之后如何进行通信

2.网络编程中的要素

  • IP 和 端口号 IP
  • 网络通信协议 udp,tcp

3.万物皆对象

  • IP和网络通信协议都可以生成对象
11.3、IP

ip地址:InetAddress

  1. 唯一定位一台网络上计算机

  2. 127.0.0.1:本机Iocalhost

  3. ip地址的分类

  • ipv4/ipv6

    • IPV4:127.0.01 ,4个字节组成,0~255,总共42亿;30亿都在北美,亚洲4亿,2011年就已经用尽
    • IPV6:128位。8个无符号整数。2001:acca:0ac1:0002:0ab7:1153:2210:ccc1
  • 公网(互联网)-私网(局域网)

    • ABCD类地址

    • 192.168.xxx.xx 专门给组织内部使用

  1. 域名:记忆IP问题
  • IP:www.xxx.com 方便记录
InetAddress  	 获取地址
InetAddress.getCanonicalHostName		规范的名字
InetAddress.getHostAddress		IP
InetAddress.getHostName		域名或自己电脑的名字


11.4、端口

端口表示计算机上的一个程序的进程;

  • 不同的进程有不同的端口!用来区分软件!

  • 被规定0~65535,不能使用相同的端口

  • TCP,UDP:65535*2,tcp : 80, udp : 80 这样不影响。单个协议下,端口号不能冲突

  • 端口分类

    • 公有端口 0~1023 (尽量不用)
      • HTTP:80
      • HTTPS:443
      • FTP:21
      • Telent:23
    • 程序注册端口:1024~49151,分配用户或者程序
      • Tomcat:8080
      • MySQL:3306
      • Oracle:1521
    • 动态、私有:49152~65535(尽量不用)
netstat -ano #查看所有的端口
netstat -ano|findstr "5900" #查看指定的端口
tasklist|findstr "8696" #查看指定端口的进程

找到电脑上特定端口,或有其对应的处理程序,才能收到发出的程序

InetSocketAddress		 IP 地址及端口
InetAddress				 IP 地址
11.5、通信协议

协议:约定,双方使用相同可识别的语言

**网络通信协议:**速率、传输码率、代码结构、传输控制… …

主要使用:**TCP/IP协议簇:**实际上是一组协议

主要:

  • TCP:用户传输协议 {类似于打电话,需要两边进行连接}
  • UDP:用户数据报协议 {类似于发短信,不需要两边连接也可以发出,但不一定能送到}

出名的协议:

  • TCP:
  • IP:网络互连协议
11.5.1 TCP UDP 对比
  1. TCP:打电话
  • 连接、稳定
  • 三次握手 四次挥手
最少需要三次,保证稳定连接
A——	我要连接 ——>B
B—— 你可以连接 ——>A
A—— 那我连接了 ——>B
连接成功!

四次挥手
A——我要断开——>B
B——你可以断开——>A

B——你确定断开?——>A
A——我确定断开!——>B
连接断开

  • 客户端、服务端:主动和被动的过程
  • 传输完成,释放连接,效率低
  1. UDP:发短信
    • 不连接、不稳定
    • 客户端、服务端:没有明确的界限
    • 不管有没有准备好,都可以发给你
    • DDOS:洪水攻击!(饱和攻击)
11.6、TCP

服务器

  • 建立服务的端口 ServerSocket
  • 等待用户的连接 accept
  • 接收用户的信息
11.6.1文件上传

服务器端

    import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpClientDemo02 {
    public static void main(String[] args) throws IOException {
        //1.创建服务
        ServerSocket serverSocket = new ServerSocket(9000);
        //2.监听客户端的连接
        System.out.println("等待连接");
        Socket socket = serverSocket.accept();  //阻塞式监听,会一直等待客户端连接
        //3.获取输入流
        InputStream is = socket.getInputStream();
        //文件输出
        FileOutputStream fos = new FileOutputStream(new File("666.jpg"));
        byte[] buffer = new byte[1024];
        int len;
        while ((len=is.read(buffer))!=-1){
            fos.write(buffer,0,len);
        }
        /*
            客户端传输完了,服务器接收完
            通知客户端我接收完毕了
         */
        //服务器端接收完毕,并回复信息
        OutputStream os = socket.getOutputStream();
        os.write("我接收完毕,你可以断开".getBytes());
    //关闭资源
    os.close();
    fos.close();
    is.close();
    socket.close();
    serverSocket.close();

}}

客户端

   import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

public class TcpServerDemo02 {
    public static void main(String[] args) throws Exception {
        //1.创建一个Socket连接
        // Socket(主机--端口号)
        //InetAddress.getByNam(主机--IP地址)
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);
        //2.创建一个输出流
        OutputStream os = socket.getOutputStream();
        //3.读取文件
        FileInputStream fis = new FileInputStream(new File("321.jpg"));
        //4.写出文件
        byte[] buffer = new byte[1024];
        int len;
        while((len = fis.read(buffer))!=-1){
            os.write(buffer,0,len);
        }
        /*
            传输完后,通知服务器
            确定服务器接收完毕,才能断开连接
         */
        //客户端已经传输完毕
        socket.shutdownOutput();
        //接收服务端完毕信息
        InputStream inputStream = socket.getInputStream();
        //由于收到的式String byte[]数组,使用byte输出管道流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer2 = new byte[1024];
        int len2;
        while((len2 = inputStream.read(buffer))!=-1){
            baos.write(buffer,0,len2);
        }
        System.out.println(baos.toString());
   //关闭资源
    baos.close();
    inputStream.close();
    fis.close();
    os.close();
    socket.close();
}}
FileOutputStream		文件输出流
FileInputStream			文件输入流
shutdownOutput			停止输出
11.6.2Tomcat

服务端

  • 自定义 S
  • Tomcat 服务器 S :Java 后台开发!

客户端

  • 自定义 C
  • 浏览器 B
11.7、DUP

发短信:不用连接,只需要知道对方的地址!

11.7.1发送接收消息
11.7.2多线程发送接收消息

发送线程

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;

public class TalkSend implements Runnable{
        DatagramSocket socket = null;
        BufferedReader reader = null;

        private int fromPort;
        private String toIP;
        private int toPort;


    public TalkSend(int fromPort, String toIP, int toPort) {
        this.fromPort = fromPort;
        this.toIP = toIP;
        this.toPort = toPort;
    
        try {
            socket = new DatagramSocket(fromPort);
        } catch (SocketException e) {
            e.printStackTrace();
        }


    }
    
    @Override
    public void run() {
    
        try {
            while(true){
                //准备数据:控制台读取 System.in
                BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                //发包内数据
                String data = reader.readLine();
                byte[] datas = data.getBytes();
                DatagramPacket packet = new DatagramPacket(datas,0,datas.length,new InetSocketAddress(this.toIP,this.toPort));
                //发送包
                socket.send(packet);
    
                if (data.equals("bye")){
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    
        //关闭流
        socket.close();
    }

}

接收线程

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class TalkReceive implements Runnable{
    DatagramSocket socket =null;
    private int port;
    private String msgfrom;

    public TalkReceive(int port, String msgfrom) {
        this.port = port;
        this.msgfrom = msgfrom;
    
        try {
            socket = new DatagramSocket(port);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void run() {
        try {
            while(true){
                //准备接收包裹
                byte[] container = new byte[1024];
                DatagramPacket packet = new DatagramPacket(container,0,container.length);
                //阻塞式接收
                socket.receive(packet);
                //断开连接
                //将接收包转换为 String 格式
                byte[] data = packet.getData();
                String receiveData = new String(data,0,data.length);
                System.out.println(msgfrom+":"+receiveData);
                if (receiveData.equals("bye")){
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        socket.close();
    }

}

学生线程

public class TalkStudent {
    public static void main(String[] args) {
        //学生同时开启两个线程
        //学生自己的发送端口、接收IP、接收端口
        new Thread(new TalkSend(6666,"localhost",9999)).start();
        //学生自己接收端口、发送过来者
        new Thread(new TalkReceive(8888,"老师")).start();
    }
}

教师线程

package com.ssxxz.chat;

public class TalkTeacher {
    public static void main(String[] args) {
        //老师
        //老师自己的发送端口、接收IP、接收端口
        new Thread(new TalkSend(7777,"localhost",8888)).start();
        //老师自己接收端口、发送过来者
        new Thread(new TalkReceive(9999,"学生")).start();
    }
}
11.8、URL

https://www.baidu.com/

统一资源定位符:定位资源的,定位互联网上的某一个资源

DNS 域名解析:www.baidu.com == xxx.x…x…x 的IP号

协议:(https) //ip地址:端口/项目名/资源

URL常用方法


public class DRLDemo01 {
    public static void main(String[] args) throws MalformedURLException {
        URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=kuangshen&password==123");

        //协议http
        System.out.println(url.getProtocol());
        //主机ip,localhost
        System.out.println(url.getHost());
        //端口,8080
        System.out.println(url.getPort());
        //文件,/helloworld/index.jsp
        System.out.println(url.getPath());
        //全路径,/helloworld/index.jsp?username=kuangshen&password==123
        System.out.println(url.getFile());
        //参数,username=kuangshen&password==123
        System.out.println(url.getQuery());
    }

}

URL网络下载


public class UrlDown {
    public static void main(String[] args) throws Exception {
        //1.下载地址
        URL url = new URL("https://**1.**2.com/**/**/***.jpg");

        //2.连接到这个资源 HTTP
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        //3.输入流
        InputStream inputStream = urlConnection.getInputStream();
        //4.下载到存放地址
        FileOutputStream fos = new FileOutputStream("123.jpg");
        //5.写出数据
        byte[] buffer = new byte[1024];
        int len ;
        while((len = inputStream.read(buffer))!=-1){
            fos.write(buffer,0,len);
        }
        fos.close();
        inputStream.close();
        urlConnection.disconnect(); //断开连接
    
    }
    }



URL			统一资源定位符
.openConnection				打开连接
HttpURLConnection			指定协议HTTP
11.9、TCP通信:关于群聊功能的简单实现

案例:综合多线程、网络、IO流的一个小案例实现

拓展:测试框架:Junit

  • 开发者可以通过【断言机制】预测程序运行的结果
  • 常见注解:
    在这里插入图片描述

12、注解和反射

12.1、注解概念

Annotation是从JDK5.0开始引入的新技术
Annotation的作用:

可以对程序作出解释,这一点和注释comment类似
对程序进行检查和约束,例如@Override
可以被其他程序(比如:编译器等)读取

Annotation的格式:

注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如:@SuppressWarning(value=”unchecked”)
Annotation在哪里使用?
可以附加在package、class、method、field等上面,相等于给他们添加了额外的辅助信息,然后结合反射机制实现对这些元数据的访问

12.2:内置注解
12.2.1、内置注解

@Override:定义在java.lang包中,此注解只适用于修饰方法,表示该方法打算重写父类中的同名方法,并且具有检查作用

@Deprecated的程序元素是程序员不鼓励使用的程序元素,通常是因为它是危险的,或者因为它已经过时了,然后存在更好的替代方法,但是你使用也没有任何影响,

@SupressWarnings:
@SuppressWarnings(“all”) // 压制所有警告,下面有例子:
@SuppressWarnings(“unchecked”) // 压制"unchecked"警告
@SuppressWarnings(value={“unchecked”, “deprecation”}) // 压制"unchecked", "deprecation"警告
注意

public @interface SuppressWarnings {
String[] value();
},

其中value是参数名,而String[]是参数是类型,当注解只使用value这一个参数的时候,value可以省略,例如上面的@SuppressWarnings(“all”)和@SuppressWarnings(“unchecked”)就是省略了前面的value,但是仅限参数名是value,并且注解中只使用value这一个参数的情况才可以 省略value

12.3:什么是元注解
12.3.1、元注解

元注解作用:负责注解其他注解,Java中定义了4个标准的元注解()类型,他们被用来对其他注解类型进行解释说明限制

元注解位置:在java.lang.annotation包中

元注解四大类型

@Target:

用于描述注解的使用范围,也就是描述注解可以使用在什么地方,public @interface Target {ElementType[] value();}可以看出可以它是一个数组,可以有多个值,里面的值设置的都是定义的注解MyAnnotation的使用范围,比如在类上、方法上等等,你可以通过@Target,然后点击里面的ElementType去查看

@Retention:

表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE < CLASS < RUNTIME),SOURCE是源码级别,CLASS 是源码级别和编译之后的字节码文件中有效,RUNTIME是在源码级别、编译之后的字节码文件、JVM中运行都有效,最常用的就是RUNTIME

@Documented:

说明该注解将被包含在javadoc文档中

@Inherited:

说明子类可以继承父类中的该注解

12.4:自定义注解
12.4.1、自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
@interface 用来声明一个注解,格式是:修饰符 @interface 注解名{定义内容}

12.5:反射概述
12.5.1、反射概念

Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并且能直接操作任意对象的内部属性以及方法
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子可以看到类的结构,这就是反射

12.6:获取反射对象
12.6.1、反射机制提供的功能

在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
……

12.6.2、反射相关的主要API

java.lang.Class:代表一个类,它就是Object类getClass()方法的返回值,它是唯一的所以类都指向它
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
……

12.6.3、反射初体验
    public static void main(String[] args) throws ClassNotFoundException {
        // 通过反射返回类的Class对象,这里的Class就是Object类中getClass()方法的返回值Class是一样的,获取的c1是Class类的对象
        Class c1 =  Class.forName("com.atguigu.demo.User");
        System.out.println(c1);

        // 一个类在方法区中只有一个Class对象
        // 一个类被加载之后,类的整个结构(构造器、方法、属性等等)都会被封装在Class对象中
        Class c2 =  Class.forName("com.atguigu.demo.User");
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
    }

}

@Data
class User{
    private Integer id;
    private String name;
    private Integer age;
}

结果:

class com.atguigu.demo.User
1066516207
1066516207

####12.7、Class类详解

12.7.1:得到Class类的几种方式

Class类:

  • Class本身也是一个类,不过这个类只有一个,所有的类都执行它,例如User类、Person类等
  • Class对象是Class类的对象,一个加载的类(例如User.java)在JVM中只会有一个Class对象
  • 一个Class对象对应的是一个加载到JVM中的对应的XXX.class文件
  • 每个类(例如User.java)的实例(例如User u = new User()中的u)都会记着自己是由哪个Class对象所生成
  • 通过CLass对象可以完整地得到一个类(例如User.java)中所有被加载的信息
    Class类(只有一个,和Object类中getClass()方法中返回的Class是同一个)是Reflection的根源,针对任何你想动态加载、运行的类、唯有先获得类(例如User.java)对应的Class对象

以上说了三个部分:Class类、Class对象、类、类的实例,通过类可以创建类的实例,通过Class类可以获取某类的Class对象,通过Class对象可以获取类中的所有信息(方法、属性等等),当然也可以创建类的实例,另外Class对象是类具有的,类可以做的它也能做
Class类的常用方法

获取Class类的实例

通过类的class属性获取,该方法最为安全可靠,程序性能最高
Class c1 = User.class;
1
通过类的实例中的getClass()方法获取
Class c2 = user.getClass();
1
通过类的全限定类名获取
Class c3 = Class.forName("com.atguigu.demo.User");
1
4.通过基本内置类型的包装类的Type属性(了解)

Class c4 = Integer.TYPE;
1
通过ClassLoader类加载器获取
Constructor constructor = c1.getDeclaredConstructor(String.class);
Class c5 = constructor.getDeclaringClass();
System.out.println(c5.hashCode());
12.8、动态代理设计模式及其应用前景

在这里插入图片描述
代理模式的基本思想是通过这一设计将主体的一部分的职责交给代理,使得主体能够更加专注某一个功能

13、JUC详细学习

13.0、多线程
13.0.1、创建线程的三种方法及其区别
  • 继承Thread类

    • 先继承 Thread类,并且重写run方法
    • 创建对象(线程对象)
    • 调用start方法,来启动线程任务
    • 在这里插入图片描述
  • 实现Runnable接口

    • 实现Runnable接口,并重写 run方法
    • 创建该类的实例对象,将该对象作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象;
    • 调用线程对象的start()方法启动该线程;
    • 在这里插入图片描述
  • 使用Callable接口

    • 创建Callable接口实现类,并实现call()方法,该方法将作为线程执行体,且该方法有返回值,再创建Callable实现类的实例;
    • 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
    • 使用FutureTask对象作为Thread对象的target创建并启动新线程,调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
    • 在这里插入图片描述

创建线程的三种方式的对比:

1.实现Runnable或Callable接口比继承Thread类的优势
(1)Runnable或Callable接口适合多个线程进行资源共享
(2)java中单一继承,但是多接口实现,提高扩展性
(3)增加程序的健壮性,代码和数据独立
(4)线程池只能放入Runable或Callable接口实现类,不能直接放入继承Thread的类
2.Callable和Runnable之间的区别
(1) Callable重写的是call()方法,Runnable重写的方法是run()方法
(2) call()方法执行后可以有返回值,run()方法没有返回值
(3) call()方法可以抛出异常,run()方法不可以
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果 。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果

13.0.2、线程安全 && 线程同步
  • 多个线程同时修改同一共享资源的时候,会出现线程安全问题。
  • 读数据是绝对不会出现线程安全问题的,它一定是因为同时在修改。
  • 一旦线程同步了,就是解决了安全问题了。
  • CPU负责调度线程执行的,它是控制中心。
  • 多线程访问临界资源时数据的安全问题,产生原因:有多个线程在同时访问同一共享资源,如果一个线程在取值的时候,时间片又被其他的线程抢走了,临界资源问题就产生了!

综上所述:线程安全问题出现的原因主要分为以下几点:

  • 存在多线程并发
  • 同时访问并存在修改同一共享资源

线程同步

  • 线程同步是为了解决线程安全问题而来的,实现基本思路是使得多线程之间总体按照一前一后的顺序执行,而不是竞争执行

那么如何才能保证线程安全呢?

  • 让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
  • 把操作共享数据的代码给上锁!

线程同步的核心思想

  • 加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。其他线程就算抢夺到了CPU的执行权,它也得在外面等着,它进不来!让所有的线程在核心代码当中能轮流执行!
  • 注意:synchronized的锁对象,它一定要是唯一的!如果锁对象不唯一,导致一个线程进一个锁,那么这个锁就没有意义了!

锁:

  • JVM,Java虚拟机级别的锁,同一个Java虚拟机里面同一个Java程序才能生效。
  • 锁:对象锁、类锁(类锁也是对象锁,它是一种特殊的对象锁)
    • 对象锁,就是任何一个实例对象,都可以看成是一把锁。
    • 类锁:类名.class,任何一个类,只要类是相同的,那么它们的类名.class是唯一的实例对象!

如何保证线程的安全?
线程同步,本质就是让执行访问共享资源这段代码的时候,只有1个线程可以执行。
可以让线程之间通信,线程通信也能解决线程安全问题。

线程同步解决线程安全问题了有什么缺点吗?
运行速度变慢,因为同一时间只有一个线程在执行的,本来是3个5个窗口一起卖票的,而现在呢是一个窗口一个窗口卖,我卖的时候其他窗口给我暂停卖票,我卖完了你才能卖,这样的话性能会降低!
**总结:**多线程同步解决线程安全问题的缺点:性能会降低!

同步方式: 同步代码块、同步方法、lock锁

案例:StringBuilder和StringBuffer

  • 二者的API一模一样。
  • StringBuilder是线程不安全的,StringBuffer是线程安全的。
  • StringBuffer的源码里面方法都是同步方法,加了synchronized修饰。
  • 使用场景区分:
    • 如果你的代码是单线程的,不需要考虑多线程当中数据安全的抢矿,你就用StringBuilder就可以了。
    • 如果说你是多线程环境下需要考虑数据安全,那么就可以选择StringBuffer。
13.0.3、线程通信、线程池
13.0.4、并发、并行、生命周期、乐观锁
13.1、什么是JUC

寻找来源:源码+官方文档

面试高频问JUC~!

  • JUC是 java.util.concurrent

  • java.util 是Java的一个工具包~

业务:普通的线程代码 Thread

Runnable: 没有返回值、效率相比于Callable 相对较低!

13.2、线程和进程

进程:一个程序,QQ.EXE Music.EXE;数据+代码+pcb

  1. 一个进程可以包含多个线程?

    • 至少包含一个main线程!
  2. Java默认有几个线程?

    • 2个线程!即为 main线程、GC线程

线程:开了一个进程Typora,写字,等待几分钟会进行自动保存(线程负责的)

对于Java而言开启线程的三种方式:Thread、Runable、Callable

提问?JAVA真的可以开启线程吗? 开不了的!

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}
**这是一个C++底层,Java是没有权限操作底层硬件的**
private native void start0();

Java是没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。

13.2.1、并发、并行
  1. 并发: 多线程操作同一个资源。

    • CPU 只有一核,模拟出来多条线程,天下武功,唯快不破。那么我们就可以使用CPU快速交替,来模拟多线程。
  2. 并行: 多个人一起行走

    • CPU多核,多个线程可以同时执行。 我们可以使用线程池!
public class Test1 {
    public static void main(String[] args) {
        //获取cpu的核数
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}
  1. 并发编程的本质:充分利用CPU的资源!
13.2.2线程有几个状态?

线程的状态:6个状态

   public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
    	//运行
        NEW,
   /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
	//运行
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
	//阻塞
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
	//等待
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
	//超时等待
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
	//终止
    TERMINATED;
}
13.2.3、wait/sleep的区别
  • 来自不同的类
wait => Object类

sleep => Thread

一般情况企业中使用休眠是:

TimeUnit.DAYS.sleep(1); //休眠1天
TimeUnit.SECONDS.sleep(1); //休眠1s
  • 关于锁的释放

    • wait 会释放锁;

    • sleep睡觉了,不会释放锁;

  • 使用的范围是不同的

    • wait 必须在同步代码块中;

    • sleep 可以在任何地方睡;

  • 是否需要捕获异常

    • wait是不需要捕获异常;

    • sleep必须要捕获异常;

13.3、Lock锁(重点)
  • 传统的Synchronized
/**

 * 真正的多线程开发
 * 线程就是一个单独的资源类,没有任何的附属操作!
   */
   public class SaleTicketDemo01 {
   public static void main(String[] args) {
       //多线程操作
       //并发:多线程操作同一个资源类,把资源类丢入线程
       Ticket ticket = new Ticket();
new Thread(()->{
    for(int i=0;i<40;i++){
        ticket.sale();
    }
},"A").start();
new Thread(()->{
    for(int i=0;i<40;i++){
        ticket.sale();
    }
},"B").start();
new Thread(()->{
    for(int i=0;i<40;i++){
        ticket.sale();
    }
},"C").start();
}
}
//资源类
//属性+方法
//oop
class Ticket{
private int number=50;


    //卖票的方式
    // synchronized 本质:队列,锁
    public synchronized void sale(){
        if(number>0){
            System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
            number--;
        }
    }



13.3.1.Lock接口
  • 公平锁: 十分公平,必须先来后到~;

  • 非公平锁: 十分不公平,可以插队;(默认为非公平锁)

public class SaleTicketDemo02 {
    public static void main(String[] args) {
        //多线程操作
        //并发:多线程操作同一个资源类,把资源类丢入线程
        Ticket2 ticket = new Ticket2();
        new Thread(()->{for(int i=0;i<40;i++) ticket.sale(); },"A").start();
        new Thread(()->{for(int i=0;i<40;i++) ticket.sale(); },"B").start();
        new Thread(()->{for(int i=0;i<40;i++) ticket.sale(); },"C").start();
    }
}

//lock三部曲
//1、    Lock lock=new ReentrantLock();
//2、    lock.lock() 加锁
//3、    finally=> 解锁:lock.unlock();
class Ticket2{
    private int number=50;
Lock lock=new ReentrantLock();

//卖票的方式
// 使用Lock 锁
public void sale(){
    //加锁
    lock.lock();
    try {
        //业务代码
        if(number>=0){
            System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
            number--;
        }
    }catch (Exception e) {
        e.printStackTrace();
    }
    finally {
        //解锁
        lock.unlock();
    }
}
}
13.3.2.Synchronized 和 Lock区别
  • Synchronized 内置的Java关键字,Lock是一个Java类

  • Synchronized 无法判断获取锁的状态,Lock可以判断

  • Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁

  • Synchronized 线程1(获得锁->阻塞)、线程2(等待);

lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

  • Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

  • Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;

13.2.3、生产者和消费者问题!

我们这次使用lock版本

  • Synchronized版本
  • wait notify可以实现,该方法是传统版本;
  public class A {
    public static void main(String[] args) {
        Data data = new Data();
  new Thread(()->{for(int i=0;i<10;i++) {
        try {
            data.increment();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    },"A").start();
    new Thread(()->{for(int i=0;i<10;i++) {
        try {
            data.decrement();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }},"B").start();
}
}
class Data{
    //数字  资源类
    private int number = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        if(number!=0){
            //等待操作
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程 我+1完毕了
        this.notifyAll();
    }
    
    //-1
    public synchronized void decrement() throws InterruptedException {
        if(number==0){
            //等待操作
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程  我-1完毕了
        this.notifyAll();
    }

}
//问题存在,A线程B线程,现在如果我有四个线程A B C D!

//解决方案: if 改为while即可,防止虚假唤醒

//这样就不存在问题了:
  • JUC版本的生产者和消费者问题

await、signal 替换 wait、notify

通过Lock找到Condition

   public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
   new Thread(()->{for(int i=0;i<10;i++) {
        data.increment();
    }
    },"A").start();
    new Thread(()->{for(int i=0;i<10;i++) {
        data.decrement();
    }},"B").start();
    new Thread(()->{for(int i=0;i<10;i++) {
        data.increment();
    }
    },"C").start();
    new Thread(()->{for(int i=0;i<10;i++) {
        data.decrement();
    }
    },"D").start();
}
}
class Data2{
    //数字  资源类
    private int number = 0;

    //lock锁
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    
    //+1
    public void increment()  {
        lock.lock();
        try{
    
            //业务
            while (number!=0){
                //等待操作
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程 我+1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    
    //-1
    public void decrement()  {
        lock.lock();
        try{
            //业务
            while (number==0){
                //等待操作
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程 我+1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

Condition的优势:精准的通知和唤醒的线程!

如果我们要指定通知的下一个进行顺序怎么办呢? 我们可以使用Condition来指定通知进程~

/**

 * A 执行完 调用B
 * B 执行完 调用C
 * C 执行完 调用A
   */

public class C {

    public static void main(String[] args) {
        Data3 data3 = new Data3();
        new Thread(()->{
            for(int i=0;i<10;i++){
                data3.printA();
            }
        },"A").start();
        new Thread(()->{
            for(int i=0;i<10;i++){
                data3.printB();
            }
        },"B").start();
        new Thread(()->{
            for(int i=0;i<10;i++){
                data3.printC();
            }
        },"C").start();
    }

}

class Data3{
    //资源类
    private Lock lock=new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1; //1A 2B 3C

    public void printA(){
        lock.lock();
        try {
            //业务 判断 -> 执行 -> 通知
            while(number!=1){
                //等待
                condition1.await();
            }
            //操作
            System.out.println(Thread.currentThread().getName()+",AAAAA");
            //唤醒指定的线程
            number=2;
            condition2.signal(); // 唤醒2
    
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            //业务 判断 -> 执行 -> 通知
            while (number!=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+",BBBBB");
            //唤醒3
            number=3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            //业务 判断 -> 执行 -> 通知
            while(number!=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+",CCCCC");
            //唤醒1
            number=1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    }
13.2.4、八锁现象

即为8个常见的锁现象

  1. 如何判断锁的是谁!锁到底锁的是谁?
    • 锁会锁住:对象、Class

深刻理解我们的锁

问题1:

结果是:先发短信,如何再打电话!

为什么? 如果你认为是顺序在前? 这个答案是错误的!

问题2:
我们再来看:我们让发短信 延迟4s

现在结果是什么呢?

结果:还是先发短信,然后再打电话!

why?

原因:并不是顺序执行!是因为synchronized 锁的对象是方法的调用!对于两个方法用的是同一个锁,谁先拿到谁先执行!另外一个则等待!
问题3:
如果我们添加一个普通方法,那么先执行哪一个呢?

答案是:先执行hello,然后再执行发短信!原因是hello是一个普通方法,不受synchronized锁的影响,但是我发现,如果我把发短信里面的延迟4秒去掉,那么就会顺序执行,先执行发短信然后再执行hello,原因应该是顺序执行的原因吧,不是太理解。

问题4:
如果我们使用的是两个对象,一个调用发短信,一个调用打电话,那么整个顺序是怎么样的呢?

答案是:先打电话,后发短信。原因:在发短信方法中延迟了4s,又因为synchronized锁的是对象,但是我们这使用的是两个对象,所以每个对象都有一把锁,所以不会造成锁的等待。正常执行

问题5,6:
如果我们把synchronized的方法加上static变成静态方法!那么顺序又是怎么样的呢?

(1)我们先来使用一个对象调用两个方法!

答案是:先发短信,后打电话

(2)如果我们使用两个对象调用两个方法!

答案是:还是先发短信,后打电话

原因是什么呢? 为什么加了static就始终前面一个对象先执行呢!为什么后面会等待呢?

原因是:对于static静态方法来说,对于整个类Class来说只有一份,对于不同的对象使用的是同一份方法,相当于这个方法是属于这个类的,如果静态static方法使用synchronized锁定,那么这个synchronized锁会锁住整个对象!不管多少个对象,对于静态的锁都只有一把锁,谁先拿到这个锁就先执行,其他的进程都需要等待!

问题7:
如果我们使用一个静态同步方法、一个同步方法、一个对象调用顺序是什么?

明显答案是:先打电话,后发短信了。

因为一个锁的是Class类模板,一个锁的是对象调用者。后面那个打电话不需要等待发短信,直接运行就可以了。

问题8:
如果我们使用一个静态同步方法、一个同步方法、两个对象调用顺序是什么呢?

当然答案是:先打电话、后发短信!

因为两个对象,一样的原因:两把锁锁的不是同一个东西,所以后面的第二个对象不需要等待第一个对象的执行。

13.2.5小结

new 出来的 this 是具体的一个对象

static Class 是唯一的一个模板

13.4、集合类不安全
13.4.1、List不安全

我们来看一下List这个集合类:

//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {
    public static void main(String[] args) {

        List<Object> arrayList = new ArrayList<>();
    
        for(int i=1;i<=10;i++){
            new Thread(()->{
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            },String.valueOf(i)).start();
        }
    
    }

}

会造成:

ArrayList 在并发情况下是不安全的!

解决方案:

  • 切换成Vector就是线程安全的啦!

  • 使用Collections.synchronizedList(new ArrayList<>());

public class ListTest {
    public static void main(String[] args) {

        List<Object> arrayList = Collections.synchronizedList(new ArrayList<>());
    
        for(int i=1;i<=10;i++){
            new Thread(()->{
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            },String.valueOf(i)).start();
        }
    
    }

}
  • 使用JUC中的包:List arrayList = new CopyOnWriteArrayList<>();
public class ListTest {
    public static void main(String[] args) {

        List<Object> arrayList = new CopyOnWriteArrayList<>();
    
        for(int i=1;i<=10;i++){
            new Thread(()->{
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            },String.valueOf(i)).start();
        }
    
    }

}
  1. CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略

  2. 多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题;

CopyOnWriteArrayList比Vector厉害在哪里?

    • Vector底层是使用synchronized关键字来实现的:效率特别低下。
    • CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!
13.4.2、Set不安全

和List、Set同级的还有一个BlockingQueue 阻塞队列;

Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;

解决方案:

  • 使用Collections工具类的synchronized包装的Set类
  • 使用CopyOnWriteArraySet 写入复制的JUC解决方案
//同理:java.util.ConcurrentModificationException
// 解决方案:
public class SetTest {
    public static void main(String[] args) {
//        Set<String> hashSet = Collections.synchronizedSet(new HashSet<>()); //解决方案1
        Set<String> hashSet = new CopyOnWriteArraySet<>();//解决方案2
        for (int i = 1; i < 100; i++) {
            new Thread(()->{
                hashSet.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(hashSet);
            },String.valueOf(i)).start();
        }
    }
}

HashSet底层是什么?

  • hashSet底层就是一个HashMap;
public HashSet() {
        map = new HashMap<>();
}

//add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
//hashSet就是使用了hashmap key不能重复的原理
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}
//PRESENT是什么? 是一个常量  不会改变的常量  无用的占位
private static final Object PRESENT = new Object();
13.4.3、Map不安全

回顾map的基本操作:

//map 是这样用的吗?  不是,工作中不使用这个
//默认等价什么? new HashMap<>(16,0.75);
Map<String, String> map = new HashMap<>();
//加载因子、初始化容量
  • 默认加载因子是0.75,默认的初始容量是16

  • 同样的HashMap基础类也存在并发修改异常!

public static void main(String[] args) {
        //map 是这样用的吗?  不是,工作中不使用这个
        //默认等价什么? new HashMap<>(16,0.75);
        Map<String, String> map = new HashMap<>();
        //加载因子、初始化容量
        for (int i = 1; i < 100; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }

结果同样的出现了:异常java.util.ConcurrentModificationException 并发修改异常

解决方案

  • 使用Collections.synchronizedMap(new HashMap<>());处理;
  • 使用ConcurrentHashMap进行并发处理

TODO:研究ConcurrentHashMap底层原理:
这里我们可以直接去研究一下,这个也是相当重要的。

13.5、Callable(简单)
  • 可以有返回值;
  • 可以抛出异常;
  • 方法不同,run()/call()

代码测试

  • 传统使用线程方式:
public class CallableTest {
    public static void main(String[] args) {
        for (int i = 1; i < 10; i++) {
            new Thread(new MyThread()).start();
        }
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

}
  • 使用Callable进行多线程操作:

Calleable 泛型T就是call运行方法的返回值类型;

  • 但是如何使用呢?

  • Callable怎么放入到Thread里面呢?

源码分析:

//对于Thread运行,只能传入Runnable类型的参数;

//我们这是Callable 怎么办呢?

//看JDK api文档:

//在Runnable里面有一个叫做FutureTask的实现类,我们进去看一下。

//FutureTask中可以接受Callable参数;

//这样我们就可以先把Callable 放入到FutureTask中, 如何再把FutureTask 放入到Thread就可以了。

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 1; i < 10; i++) {
//            new Thread(new Runnable()).start();
//            new Thread(new FutureTask<>( Callable)).start();
            MyThread thread= new MyThread();
            //适配类:FutureTask
            FutureTask<String> futureTask = new FutureTask<>(thread);
            //放入Thread使用
            new Thread(futureTask,String.valueOf(i)).start();
            //获取返回值
            String s = futureTask.get();
            System.out.println("返回值:"+ s);
        }
    }
}

class MyThread implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("Call:"+Thread.currentThread().getName());
        return "String"+Thread.currentThread().getName();
    }

}

这样我们就可以使用Callable来进行多线程编程了,并且我们发现可以有返回值,并且可以抛出异常。

注意两个重点:

13.6、常用的辅助类(必会!)
13.6.1 CountDownLatch

其实就是一个减法计数器,对于计数器归零之后再进行后面的操作,这是一个计数器!

//这是一个计数器  减法
public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        //总数是6
        CountDownLatch countDownLatch = new CountDownLatch(6);
    
        for (int i = 1; i <= 6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" Go out");
                countDownLatch.countDown(); //每个线程都数量-1
            },String.valueOf(i)).start();
        }
        countDownLatch.await();  //等待计数器归零  然后向下执行
    
        System.out.println("close door");
    
    }

}

主要方法:

  • countDown 减一操作;
  • await 等待计数器归零。
  • await等待计数器为0,就唤醒,再继续向下运行。
13.6.2 CyclickBarrier

其实就是一个加法计数器;

public class CyclicBarrierDemo {
    public static void main(String[] args) {

        //主线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙~");
        });
    
        for (int i = 1; i <= 7; i++) {
            //子线程
            int finalI = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 收集了第 {"+ finalI+"} 颗龙珠");
                try {
                    cyclicBarrier.await(); //加法计数 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    
    }

}
13.6.3 Semaphore

Semaphore:信号量

抢车位:

3个车位 6辆车:

public class SemaphoreDemo {
    public static void main(String[] args) {
        //停车位为3个
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    semaphore.acquire(); //得到
                    //抢到车位
                    System.out.println(Thread.currentThread().getName()+" 抢到了车位{"+ finalI +"}");
                    TimeUnit.SECONDS.sleep(2); //停车2s
                    System.out.println(Thread.currentThread().getName()+" 离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }
    }
}

原理:

  • semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!

  • semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!

  • 作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!

13.7、读写锁

先对于不加锁的情况:

  • 如果我们做一个我们自己的cache缓存。分别有写入操作、读取操作;

  • 我们采用五个线程去写入,使用十个线程去读取。

我们来看一下这个的效果,如果我们不加锁的情况!

package com.ogj.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
        //开启5个线程 写入数据
        for (int i = 1; i <=5 ; i++) {
            int finalI = i;
            new Thread(()->{
                mycache.put(String.valueOf(finalI),String.valueOf(finalI));
            }).start();
        }
        //开启10个线程去读取数据
        for (int i = 1; i <=10 ; i++) {
            int finalI = i;
            new Thread(()->{
                String o = mycache.get(String.valueOf(finalI));
            }).start();
        }
    }
}

class MyCache_ReadWriteLock{
    private volatile Map<String,String> map=new HashMap<>();

    //使用读写锁
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //普通锁
    private Lock lock=new ReentrantLock();
    
    public void put(String key,String value){
        //写入
        System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
        map.put(key, value);
        System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
    }
    
    public String get(String key){
        //得到
        System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
        String o = map.get(key);
        System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
        return o;
    }

}
  • 所以如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。

  • 我们也可以采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。

  • 但是这次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证

package com.ogj.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
        //开启5个线程 写入数据
        for (int i = 1; i <=5 ; i++) {
            int finalI = i;
            new Thread(()->{
                mycache.put(String.valueOf(finalI),String.valueOf(finalI));
            }).start();
        }
        //开启10个线程去读取数据
        for (int i = 1; i <=10 ; i++) {
            int finalI = i;
            new Thread(()->{
                String o = mycache.get(String.valueOf(finalI));
            }).start();
        }
    }
}

class MyCache_ReadWriteLock{
    private volatile Map<String,String> map=new HashMap<>();

    //使用读写锁
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //普通锁
    private Lock lock=new ReentrantLock();
    
    public void put(String key,String value){
        //加锁
        readWriteLock.writeLock().lock();
        try {
            //写入
            //业务流程
            System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock(); //解锁
        }
    }
    
    public String get(String key){
        //加锁
        String o="";
        readWriteLock.readLock().lock();
        try {
            //得到
            System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
            o = map.get(key);
            System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
        return o;
    }

}

以上 整个过程没有再出现错乱的情况,对于读取,我们运行多个线程同时读取,

因为这样不会造成数据不一致问题,也能在一定程度上提高效率

13.8、阻塞队列
  1. 阻塞队列jdk1.8文档解释:
  • BlockingQueue
    • blockingQueue 是Collection的一个子类;
  1. 什么情况我们会使用 阻塞队列呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Z9XDqG1-1648359484099)(C:\Users\南风晨明\AppData\Roaming\Typora\typora-user-images\image-20210729164655223.png)]

  • 多线程并发处理、线程池!

整个阻塞队列的家族如下:

Queue以下实现的有Deque、AbstaractQueue、BlockingQueue;

BlockingQueue以下有Link链表实现的阻塞队列、也有Array数组实现的阻塞队列

  1. 如何使用阻塞队列呢?
  • 操作:添加、移除

但是实际我们要学的有:

13.8.1四组API
/**
     * 抛出异常
     */
    public static void test1(){
        //需要初始化队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        //抛出异常:java.lang.IllegalStateException: Queue full

//        System.out.println(blockingQueue.add("d"));
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        //如果多移除一个
        //这也会造成 java.util.NoSuchElementException 抛出异常
        System.out.println(blockingQueue.remove());

    }
=======================================================================================

/**
     * 不抛出异常,有返回值
     */
    public static void test2(){
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        //添加 一个不能添加的元素 使用offer只会返回false 不会抛出异常
        System.out.println(blockingQueue.offer("d"));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        //弹出 如果没有元素 只会返回null 不会抛出异常
        System.out.println(blockingQueue.poll());
    }

=======================================================================================
/**
     * 等待 一直阻塞
     */
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        //一直阻塞 不会返回
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
    
        //如果队列已经满了, 再进去一个元素  这种情况会一直等待这个队列 什么时候有了位置再进去,程序不会停止

//        blockingQueue.put("d");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        //如果我们再来一个  这种情况也会等待,程序会一直运行 阻塞
        System.out.println(blockingQueue.take());
    }

=======================================================================================
/**

   * 等待 超时阻塞
       这种情况也会等待队列有位置 或者有产品 但是会超时结束
          */
         public static void test4() throws InterruptedException {
     ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
     blockingQueue.offer("a");
     blockingQueue.offer("b");
     blockingQueue.offer("c");
     System.out.println("开始等待");
     blockingQueue.offer("d",2, TimeUnit.SECONDS);  //超时时间2s 等待如果超过2s就结束等待
     System.out.println("结束等待");
     System.out.println("===========取值==================");
     System.out.println(blockingQueue.poll());
     System.out.println(blockingQueue.poll());
     System.out.println(blockingQueue.poll());
     System.out.println("开始等待");
     blockingQueue.poll(2,TimeUnit.SECONDS); //超过两秒 我们就不要等待了
     System.out.println("结束等待");
         }
13.9.SynchronousQueue同步队列

同步队列

  • 没有容量,也可以视为容量为1的队列;

  • 进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;

  • put方法 和 take方法;

  • Synchronized 和 其他的BlockingQueue 不一样 它不存储元素;

  • put了一个元素,就必须从里面先take出来,否则不能再put进去值!

  • SynchronousQueue 的take是使用了lock锁保证线程安全的。

/**

 * 同步队列
   */
   public class SynchronousQueueDemo {
   public static void main(String[] args) {
       BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
       //研究一下 如果判断这是一个同步队列

       //使用两个进程
       // 一个进程 放进去
       // 一个进程 拿出来
       new Thread(()->{
           try {
               System.out.println(Thread.currentThread().getName()+" Put 1");
               synchronousQueue.put("1");
               System.out.println(Thread.currentThread().getName()+" Put 2");
               synchronousQueue.put("2");
               System.out.println(Thread.currentThread().getName()+" Put 3");
               synchronousQueue.put("3");
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       },"T1").start();
       
       new Thread(()->{
           try {
               System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());

   //                TimeUnit.SECONDS.sleep(3);
               System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
   //                TimeUnit.SECONDS.sleep(3);
               System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());

           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       },"T2").start();

   }
   }
13.10、线程池(重点)

线程池:三大方法、7大参数、4种拒绝策略

池化技术

  1. 程序的运行,本质:占用系统的资源!我们需要去优化资源的使用

    • ===> 池化技术、线程池、JDBC的连接池、内存池、对象池 等等。。。。
  2. 资源的创建、销毁十分消耗资源

    • 池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。

线程池的好处:

  • 降低资源的消耗;

  • 提高响应的速度;

  • 方便管理;

  • 线程复用、可以控制最大并发数、管理线程;

线程池:三大方法

  • ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
  • ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
  • ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//工具类 Executors 三大方法;
public class Demo01 {
    public static void main(String[] args) {

        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
        ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
        ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
    
        //线程池用完必须要关闭线程池
        try {
    
            for (int i = 1; i <=100 ; i++) {
                //通过线程池创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+ " ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }

}

7大参数

源码分析
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

本质:三种方法都是开启的ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,  //核心线程池大小
                          int maximumPoolSize, //最大的线程池大小
                          long keepAliveTime,  //超时了没有人调用就会释放
                          TimeUnit unit, //超时单位
                          BlockingQueue<Runnable> workQueue, //阻塞队列
                          ThreadFactory threadFactory, //线程工厂 创建线程的 一般不用动
                          RejectedExecutionHandler handler //拒绝策略
                         ) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

阿里巴巴的Java操作手册中明确说明:对于Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池。

手动创建线程池

// todo

拒绝策略4种:

(1)new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常

超出最大承载,就会抛出异常:队列容量大小+maxPoolSize

(2)new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理

(3)new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。

(4)new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常

小结和拓展

如何去设置线程池的最大大小如何去设置?

  1. CPU密集型和IO密集型!

    • CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小

      • 我们可以使用代码来来获取逻辑处理器数量。
    • I/O密集型:

      • 在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。
13.11、四大函数式接口(必需掌握)

新时代的程序员**:lambda表达式、链式编程、函数式接口、Stream流式计算**

13.11.1、函数式接口

只有一个方法的接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
//超级多的@FunctionalInterface
//简化编程模型,在新版本的框架底层大量应用
//foreach()的参数也是一个函数式接口,消费者类的函数式接口
  • 函数型接口可以使用lambda表达式;
代码测试:

Function函数型接口



/**

 * Function函数型接口
   */
   public class Demo01 {
   public static void main(String[] args) {
       Function<String,String> function = (str) ->{return str;};
       System.out.println(function.apply("starasdas"));
   }
   }

   Predicate断定型接口



/**

 * 断定型接口:有一个输入参数,返回值只能是 布尔值!
   */
   public class Demo2 {
   public static void main(String[] args) {
       //判断字符串是否为空
       Predicate<String> predicate = (str)->{return str.isEmpty();};
       System.out.println(predicate.test("11"));
       System.out.println(predicate.test(""));
   }
   }

   Consummer 消费型接口



/**

 * 消费型接口 没有返回值!只有输入!
   */
   public class Demo3 {
   public static void main(String[] args) {
       Consumer<String> consumer = (str)->{
           System.out.println(str);
       };
       consumer.accept("abc");
   }
   }

   Supplier供给型接口



/**

 * 供给型接口,只返回,不输入
   */
   public class Demo4 {
   public static void main(String[] args) {
       Supplier<String> supplier = ()->{return "1024";};
       System.out.println(supplier.get());
   }
   }
13.11.2、Stream流式计算

什么是Stream流式计算?

存储+计算!

存储:集合、MySQL

计算:流式计算~

13.11.3、链式编程
public class Test {
    public static void main(String[] args) {
        User user1 = new User(1,"a",21);
        User user2 = new User(2,"b",22);
        User user3 = new User(3,"c",23);
        User user4 = new User(4,"d",24);
        User user5 = new User(5,"e",25);
        User user6 = new User(6,"f",26);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5, user6);

        //计算交给流
        //链式编程!!!!
        list.stream()
                .filter((u)->{ return u.getId()%2==0; })
                .filter((u)->{return u.getAge()>23;})
                .map((u)->{return u.getName().toUpperCase();})
                .sorted((uu1,uu2)->{
                    return uu2.compareTo(uu1);
                })
                .limit(1)
                .forEach(System.out::println);
    }

}
13.11.4,lambda表达式

外见于 多线程的学习

13.12、ForkJoin

什么是ForkJoin?

ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!

大数据中:MapReduce 核心思想->把大任务拆分为小任务!

ForkJoin 特点: 工作窃取!

实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!

如何使用ForkJoin?

1、通过ForkJoinPool来执行
2、计算任务 execute(ForkJoinTask<?> task)

3、计算类要去继承ForkJoinTask;

  • ForkJoin的计算类!
package com.ogj.forkjoin;

import java.util.concurrent.RecursiveTask;

public class ForkJoinDemo extends RecursiveTask<Long> {

    private long star;
    private long end;
    
    //临界值
    private long temp=1000000L;
    
    public ForkJoinDemo(long star, long end) {
        this.star = star;
        this.end = end;
    }
    
    /**
     * 计算方法
     * @return Long
     */
    @Override
    protected Long compute() {
        if((end-star)<temp){
            Long sum = 0L;
            for (Long i = star; i < end; i++) {
                sum+=i;
            }

//            System.out.println(sum);
            return sum;
        }else {
            //使用forkJoin 分而治之 计算
            //计算平均值
            long middle = (star+ end)/2;
            ForkJoinDemo forkJoinDemoTask1 = new ForkJoinDemo(star, middle);
            forkJoinDemoTask1.fork();  //拆分任务,把线程任务压入线程队列
            ForkJoinDemo forkJoinDemoTask2 = new ForkJoinDemo(middle, end);
            forkJoinDemoTask2.fork();  //拆分任务,把线程任务压入线程队列
            long taskSum = forkJoinDemoTask1.join() + forkJoinDemoTask2.join();
            return taskSum;
        }
    }
}

测试类!

package com.ogj.forkjoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }

    /**
     * 普通计算
     */
    public static void test1(){
        long star = System.currentTimeMillis();
        long sum = 0L;
        for (long i = 1; i < 20_0000_0000; i++) {
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
        System.out.println(sum);
    }
    
    /**
     * 使用ForkJoin
     */
    public static void test2() throws ExecutionException, InterruptedException {
        long star = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 20_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long aLong = submit.get();
        System.out.println(aLong);
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
    }


    /**
     * 使用Stream 并行流
     */
    public static void test3(){
        long star = System.currentTimeMillis();
        //Stream并行流()
        long sum = LongStream.range(0L, 20_0000_0000L).parallel().reduce(0, Long::sum);
        System.out.println(sum);
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
    }

}

.parallel().reduce(0, Long::sum)使用一个并行流去计算整个计算,提高效率。

reduce方法的优点:

13.13、异步回调

Future 设计的初衷:对将来的某个事件结果进行建模!

  • 其实就是前端 --> 发送ajax异步请求给后端

  • 但是我们平时都使用CompletableFuture

(1)没有返回值的runAsync异步回调

public static void main(String[] args) throws ExecutionException, InterruptedException 
{
        // 发起 一个 请求

        System.out.println(System.currentTimeMillis());
        System.out.println("---------------------");
        CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
            //发起一个异步任务
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+".....");
        });
        System.out.println(System.currentTimeMillis());
        System.out.println("------------------------------");
        //输出执行结果
        System.out.println(future.get());  //获取执行结果

 }

(2)有返回值的异步回调supplyAsync

//有返回值的异步回调
CompletableFuture<Integer> completableFuture=CompletableFuture.supplyAsync(()->{
    System.out.println(Thread.currentThread().getName());
    try {
        TimeUnit.SECONDS.sleep(2);
        int i=1/0;
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
    //success 回调
    System.out.println("t=>" + t); //正常的返回结果
    System.out.println("u=>" + u); //抛出异常的 错误信息
}).exceptionally((e) -> {
    //error回调
    System.out.println(e.getMessage());
    return 404;
}).get());

whenComplete: 有两个参数,一个是t 一个是u

T:是代表的 正常返回的结果;

U:是代表的 抛出异常的错误信息;

如果发生了异常,get可以获取到exceptionally返回的值;

13.14.JMM

什么是JMM?

JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!

关于JMM的一些同步的约定:

  • 线程解锁前,必须把共享变量立刻刷回主存;

  • 线程加锁前,必须读取主存中的最新值到工作内存中;

  • 加锁和解锁是同一把锁;

线程中分为 工作内存、主内存

8种操作:

  • Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
  • Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
  • assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
  • store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
  • write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
  • lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

JMM对这8种操作给了相应的规定:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

遇到问题:程序不知道主存中的值已经被修改过了!;

13.15、Volatile

Volatile 是 Java 虚拟机提供 轻量级的同步机制

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排
13.15.1.保证可见性
public class JMMDemo01 {

    // 如果不加volatile 程序会死循环
    // 加了volatile是可以保证可见性的
    private volatile static Integer number = 0;
    
    public static void main(String[] args) {
        //main线程
        //子线程1
        new Thread(()->{
            while (number==0){
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //子线程2
        new Thread(()->{
            while (number==0){
            }
    
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        number=1;
        System.out.println(number);
    }

}
13.15.2、不保证原子性

原子性:不可分割;

线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。

/**

 * 不保证原子性

 * number <=2w

 * */
   public class VDemo02 {

   private static volatile int number = 0;

   public static void add(){
       number++; 
       //++ 不是一个原子性操作,是两个~3个操作
       //
   }

   public static void main(String[] args) {
       //理论上number  === 20000

       for (int i = 1; i <= 20; i++) {
           new Thread(()->{
               for (int j = 1; j <= 1000 ; j++) {
                   add();
               }
           }).start();
       }
       
       while (Thread.activeCount()>2){
           //main  gc
           Thread.yield();
       }
       System.out.println(Thread.currentThread().getName()+",num="+number);

   }
   }
   
  1. 如果不加lock和synchronized ,怎么样保证原子性?
    • 解决方法:使用JUC下的原子包下的class;

代码如下:

public class VDemo02 {

    private static volatile AtomicInteger number = new AtomicInteger();
    
    public static void add(){

//        number++;
        number.incrementAndGet();  //底层是CAS保证的原子性
    }

    public static void main(String[] args) {
        //理论上number  === 20000
    
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    add();
                }
            }).start();
        }
    
        while (Thread.activeCount()>2){
            //main  gc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+",num="+number);
    }

}
  • 这些类的底层都直接和操作系统挂钩!是在内存中修改值。

  • Unsafe类是一个很特殊的存在;

  • 原子类为什么这么高级?

13.15.3、禁止指令重排

什么是指令重排?

我们写的程序,计算机并不是按照我们自己写的那样去执行的

  • 源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行

处理器在进行指令重排的时候,会考虑数据之间的依赖性!

int x=1; //1
int y=2; //2
x=x+5;   //3
y=x*x;   //4

//我们期望的执行顺序是 1_2_3_4  可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的

可能造成的影响结果:前提:a b x y这四个值 默认都是0

线程A 线程B
x=a y=b
b=1 a=2
正常的结果: x = 0; y =0;

线程A 线程B
x=a y=b
b=1 a=2
可能在线程A中会出现,先执行b=1,然后再执行x=a;

在B线程中可能会出现,先执行a=2,然后执行y=b;

那么就有可能结果如下:x=2; y=1.

volatile可以避免指令重排:

内存屏障:CPU指令,作用:

1、保证特定的操作的执行顺序!

2、可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oMXkYvsA-1648359484102)(C:\Users\南风晨明\AppData\Roaming\Typora\typora-user-images\image-20210729171848624.png)]

总结

  • volatile可以保证可见性;
  • 不能保证原子性
  • 由于内存屏障,可以保证避免指令重排的现象产生

**面试官:那么你知道在哪里用这个内存屏障用得最多呢?**单例模式

13.16、玩转单例模式
13.16.1.饿汉式、DCL懒汉式
  • 饿汉式
/**

 * 饿汉式单例
   */
   public class Hungry {

   /**

    * 可能会浪费空间
      */
      private byte[] data1=new byte[1024*1024];
      private byte[] data2=new byte[1024*1024];
      private byte[] data3=new byte[1024*1024];
      private byte[] data4=new byte[1024*1024];



    private Hungry(){
    
    }
    private final static Hungry hungry = new Hungry();
    
    public static Hungry getInstance(){
        return hungry;
    }

}
    • DCL懒汉式
//懒汉式单例模式
public class LazyMan {

    private static boolean key = false;
    
    private LazyMan(){
        synchronized (LazyMan.class){
            if (key==false){
                key=true;
            }
            else{
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName()+" ok");
    }
    private volatile static LazyMan lazyMan;
    
    //双重检测锁模式 简称DCL懒汉式
    public static LazyMan getInstance(){
        //需要加锁
        if(lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan=new LazyMan();
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     *  就有可能出现指令重排问题
                     *  比如执行的顺序是1 3 2 等
                     *  我们就可以添加volatile保证指令重排问题
                     */
                }
            }
        }
        return lazyMan;
    }
    //单线程下 是ok的
    //但是如果是并发的
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //Java中有反射

//        LazyMan instance = LazyMan.getInstance();
        Field key = LazyMan.class.getDeclaredField("key");
        key.setAccessible(true);
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); //无视了私有的构造器
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        key.set(lazyMan1,false);
        LazyMan instance = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(lazyMan1);
        System.out.println(instance == lazyMan1);
    }

}

静态内部类

//静态内部类
public class Holder {
    private Holder(){

    }
    public static Holder getInstance(){
        return InnerClass.holder;
    }
    public static class InnerClass{
        private static final Holder holder = new Holder();
    }

}

单例不安全, 因为反射

13.16.2枚举

枚举当中没有无参构造,只有两个参数的有参构造

//enum 是什么? enum本身就是一个Class 类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>()

        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }

}

使用枚举,我们就可以防止反射破坏了。

枚举类型使用JAD最终反编译后源码:

如果我们看idea 的文件:会发现idea骗了我们,居然告诉我们是有有参构造的,我们使用jad进行反编译。




public final class EnumSingle extends Enum
{

    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }
    
    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name);
    }
    
    private EnumSingle(String s, int i)
    {
        super(s, i);
    }
    
    public EnumSingle getInstance()
    {
        return INSTANCE;
    }
    
    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];
    
    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }

}
13.17、深入理解CAS

什么是CAS?

大厂必须深入研究底层!!!!修内功!操作系统、计算机网络原理、组成原理、数据结构

public class casDemo {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        //boolean compareAndSet(int expect, int update)
        //期望值、更新值
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    
        //因为期望值是2020  实际值却变成了2021  所以会修改失败
        //CAS 是CPU的并发原语
        atomicInteger.getAndIncrement(); //++操作
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }

}

总结:

CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。

缺点:

  1. 循环会耗时;
  2. 一次性只能保证一个共享变量的原子性;
  3. 它会存在ABA问题
    • CAS:ABA问题?(狸猫换太子)
线程1:期望值是1,要变成2;

线程2:两个操作:

1、期望值是1,变成3
2、期望是3,变成1
所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1public class casDemo {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    
        //boolean compareAndSet(int expect, int update)
        //期望值、更新值
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger.get());
    
        //因为期望值是2020  实际值却变成了2021  所以会修改失败
        //CAS 是CPU的并发原语

//        atomicInteger.getAndIncrement(); //++操作
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}
13.18、原子引用

解决ABA问题,对应的思想:就是使用了乐观锁~

Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。

所以如果遇到,使用大于128的时候,使用原子引用的时候,如果超过了这个值,那么就不会进行版本上升!

那么如果我们使用小于128的时候:

正常业务操作中,我们一般使用的是一个个对象,一般情况不会遇到这种情况。

13.19、各种锁的理解
13.19.1、公平锁、非公平锁
  • 公平锁:非常公平;不能插队的,必须先来后到;
/**

 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
   */
   public ReentrantLock() {
   sync = new NonfairSync();
   }
  • 非公平锁:非常不公平,允许插队的,可以改变顺序。
/**

 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
   *
 * @param fair {@code true} if this lock should use a fair ordering policy
   */
   public ReentrantLock(boolean fair) {
   sync = fair ? new FairSync() : new NonfairSync();
   }
13.19.2、可重入锁

可重入锁(递归锁)

Synchronizedpublic class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }

}

class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"=> sms");
        call();//这里也有一把锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=> call");
    }
}

13.19.3.lock锁
//lock
public class Demo02 {

    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }

}
class Phone2{

    Lock lock=new ReentrantLock();
    
    public void sms(){
        lock.lock(); //细节:这个是两把锁,两个钥匙
        //lock锁必须配对,否则就会死锁在里面
        try {
            System.out.println(Thread.currentThread().getName()+"=> sms");
            call();//这里也有一把锁
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "=> call");
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }

}

  • lock锁必须配对,相当于lock和 unlock 必须数量相同;
  • 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;
13.19.4、自旋锁
spinlock

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}
13.19.5.自我设计自旋锁:
public class SpinlockDemo {

    //int 0
    //thread null
    AtomicReference<Thread> atomicReference=new AtomicReference<>();
    
    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"===> mylock");
    
        //自旋锁
        while (!atomicReference.compareAndSet(null,thread)){
            System.out.println(Thread.currentThread().getName()+" ==> 自旋中~");
        }
    }


    //解锁
    public void myunlock(){
        Thread thread=Thread.currentThread();
        System.out.println(thread.getName()+"===> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }

}

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        reentrantLock.unlock();


        //使用CAS实现自旋锁
        SpinlockDemo spinlockDemo=new SpinlockDemo();
        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myunlock();
            }
        },"t1").start();
    
        TimeUnit.SECONDS.sleep(1);


        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myunlock();
            }
        },"t2").start();
    }

}

运行结果:

t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。

13.19.6死锁

死锁是什么?

死锁测试,怎么排除死锁?:

package com.ogj.lock;

import java.util.concurrent.TimeUnit;

public class DeadLock {
    public static void main(String[] args) {
        String lockA= "lockA";
        String lockB= "lockB";

        new Thread(new MyThread(lockA,lockB),"t1").start();
        new Thread(new MyThread(lockB,lockA),"t2").start();
    }

}

class MyThread implements Runnable{

    private String lockA;
    private String lockB;
    
    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }
    
    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);
            }
        }
    }

}

解决问题:

1、使用jps定位进程号,jdk的bin目录下: 有一个jps

命令:jps -l

2、使用jstack 进程进程号 找到死锁信息

14、Jvm

;