java基础
变量的命名规范
- 所有的变量、方法、类名:见名知意
- 类成员变量:首字母小写和驼峰原则
- 局部变量:首字母小写和驼峰原则
- 常量:大写字母和下划线
- 类名:首字母大写和驼峰原则
- 方法名:首字母小写和驼峰原则
java语言支持如下运算符
优先级()
-
算术运算符:+,-,*,/(除),%(取余)java中叫模运算,++(自增), --(自减)
//++ -- 自增 自减 一元运算 int a=3; int b=a++; //先给b赋值,再自增 int c=++a;//先自增,再给b赋值 System.out.println(a); //5 System.out.println(b); //3 System.out.println(c); //5 //幂运算2^3 2*2*2 = 8 很多运算用工具类来操作 double pow = Math.pow(2, 3); System.out.println(pow); //8
-
赋值运算符:=
-
关系运算符:>, <, >=, <=, ==, !=instanceof
-
逻辑运算符:&& 与, || 或 ,!非
//与(and) 或(or) 非(取反) boolean a = true; boolean b = false; System.out.println(a&&b); //false 两个变量都为真,结果才为true -----(一假为假) System.out.println(a||b);//true 有一个为真,结果才为true -----(一真为真) System.out.println(!(a&&b));//true 假为真,真为假 //短路运算 int c = 5; boolean d = (c<4)&&(c++<4); //5 c++没有运算
-
位运算符:&,|, ^, ~, >>, <<, >>>
/* A = 0011 1100 B = 0000 1101 ---------------- A&B = 0000 1100 (A与B 两个位置都是1的时候才是1,否则为0) A|B = 0011 1101 (A或B 一个位置是1才是1,否则为0) A^B = 0011 0001 (按位异或 两个位置相同为0,否则为1) ~B = 1111 0010 (按位取反,和B相反) --------------------------------- 2*8 怎么算最快 << 左移 相当于*2 >> 右移 相当于/2 0000 0000 0 0000 0001 1 0000 0010 2 0000 0011 3 0000 0100 4 0000 1000 8 0001 0000 16 */ System.out.println(2<<3); //16
-
条件运算符:?:
// X ? Y : Z //如果X==true,则结果为Y,否则结果为Z
-
扩展赋值运算符:+=, -=,*=,/=
int a = 10; int b = 20; a+=b; //a=a+b a-=b; //a=a-b //字符串连接符 System.out.println(""+a+b); //1020 System.out.println(a+b+""); //30
阅读阿里巴巴开发手册 java API帮助文档
javaDoc
-
javadoc命令是用来生成自己API文档
cmd命令:
javadoc -encoding UTF-8 -charset UTF-8 Doc.java
-
参数信息
- @author 作者名
- @verdion 版本号
- @since指明需要最早使用的jdk版本
- @param参数名
- @return返回值情况
- throws异常抛出情况
- 学会查找使用IDEA产生javaDoc文档
java流程控制
1.用户交互Scanner对象
通过Scanner类来获取的用户的输入
基本语法:
Scanner s = new Scanner(System.in);
2.顺序结构
java的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行。
顺序结构是最简单的算法结构。
语句与语句之间,框与框之间是按从上到下的顺序进行的,它是由若干个依次执行的处理步骤组成的,它是任何一个算法都离不开的一种基本算法结构
3.选择结构
-
if单选择结构
语法:
if (布尔表达式){ //如果布尔表达式为true将执行的语句 }
-
if双选择结构
语法:
if (布尔表达式){ //如果布尔表达式为true将执行的语句 }else { //如果布尔表达式为false将执行的语句 }
-
if多选择结构
语法:
if (布尔表达式1){ //如果布尔表达式1为true将执行的语句 }else if (布尔表达式2){ //如果布尔表达式2为true将执行的语句 }else if (布尔表达式3){ //如果布尔表达式3为true将执行的语句 }else { //如果以上布尔表达式都不为true执行代码 }
-
嵌套的if结构
if (布尔表达式1) { //如果布尔表达式1为true将执行的语句 if (布尔表达式2){ //如果布尔表达式2为true将执行的语句 } }
-
switch多选择结构
switch (expression) { case value: //语句 break;//可选 case value: //语句 break;//可选 //可以任意数量的语句 default://可选 //语句 }
字符的本质还是数字
反编译: Java----->calss(字节码文件)------->反编译(IDEA)
4.循环结构
-
while循环
while(布尔表达式){ //循环内容 }
-
do…while循环
do { //代码语句 }while(布尔表达式); do...while总是保证循环体会被至少执行一次!
-
for循环
for (初始化;布尔表达式;迭代){ //代码语句 }
练习一:计算0~100之间的偶数和奇数和
练习二:用while或for循环输出1- 1000之间能被5整除的数,并且每行输出3个
练习三:打印九九乘法表(1. 打印第一列 2.把固定的1再用一个循坏包起来3.去点重复项,i<=j)拆分问题
-
java5中引入了一种主要用于数组的增强for循环
for(声明语句:表达式){ //代码语句 } 主要用于遍历数组或集合
5.break & continue
break用于强行退出循坏,不执行循环中剩余的语句。
continue用于在循坏语句体中,用于终止某次循坏过程,即跳过循坏体中尚未执行的语句,接着进行下一次是否执行循坏的判定。
goto关键字
6.练习
-
打印三角形
//打印三角形 for (int i = 1; i <=5; i++) { for (int j = 5; j >=i ; j--) { System.out.print(" "); } for (int j = 1; j <=i ; j++) { System.out.print("+"); } for (int j = 1; j < i; j++) { System.out.print("+"); } System.out.println(); }
java方法详解
1.何谓方法
java方法是语句的集合,它们在一起执行一个功能
- 是解决一类问题的步骤的有序组合
- 方法包含于类或对象中
- 在程序中创建,在其他地方被引用
设计方法的原则:最好保持方法的原子性,就是一个方法只完成一个功能,这样有利于后期的扩展
2.方法的定义及调用
-
java的方法类似于其它语言的函数,是一段用来完成特定功能的代码片段
-
方法的所有部分
修饰符:修饰符,这是可选的,定义了该方法的访问类型
返回值类型:方法可能会返回值。没有返回值是关键字void
方法名:是方法的实际名称
参数类型:参数像是一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数。
- 形式参数:在方法被调用时用于接收外界输入的数据。
- 实参:调用方法时实际传给方法的数据
方法体:方法体包含具体的语句,定义该方法的功能。
break:跳出switch,结束循环
修饰符 返回值类型 方法名(参数类型 参数名){ .... 方法体 .... return 返回值; }
java是值传递 引用传递
https://www.cnblogs.com/zhangshiwen/p/5830062.html
3.方法重载
- 重载就是在一个类中,有相同的函数名称,但形参不同的函数
- 方法的重载的规则:
- 方法名称必须相同
- 参数列表必须不同(个数不同、或类型不同、参数排列顺序不同等)。
- 方法的返回类型可以相同也可以不相同
- 仅仅返回类型不同不足以成为方法的重载
4.命令行传参
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args["+i+"]:"+args[i]);
}
}
}
- Javac 文件名.java ------对文件进行编译
- java 文件名 参数
5.可变参数(不定向)
- 支持传递同类型的可变参数给一个方法
- 在方法声明中,在指定参数类型后加一个省略号(…)
- z一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明
6.递归
递归就是:自己调用自己
利用递归可以用简单的程序来解决一些复杂的问题。递归的能力在于用有限的语句来定义对象的无限集合。
递归结构包括两个部分:
递归头:==什么时候不调用自身方法。如果没有头,将陷入死循环。==
递归体:==什么时候需要调用自身方法。==
public static void main(String[] args) {
System.out.println(f(4));
}
//1! 1
//2! 2*1
//3! 3*2*1
//4! 4*3*2*1
//n!= n*(n-1)
public static int f(int n){
if (n==1){
return 1;
}else {
return n*f(n-1);
}
}
力扣刷题
数组
1.数组概述
- 数组是相同类型数据的有序集合
- 按照一定的向后次序排列组合而成
- 每个数组元素通过一个下标来访问它们,从来0开始
四个基本特点
- 长度是确定的,数组一旦被创建,它的大小是不可以改变的
- 元素必须是相同类型,不允许出现混合类型
- 数组中的元素可以是任何数据类型,包括基本数据类型和引用类型
- 数组变量属于引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量
数组本省就是对象,java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,==数组对象本身是在堆中的。
2.数组声明创建
dataType[] arrayRefVar = new dataType[arraySize];
3.数组使用
- For-Each循环
- 数组作为方法入参
- 数组作返回值
int[] arrays = {1, 2, 3, 4, 5};
//取不到下标
for (int array : arrays) {
System.out.println(array);
}
int[] reverse = reverse(arrays);
}
//反转数组
public static int[] reverse(int[] arrays){
int[] result = new int[arrays.length];
//反转的操作
for (int i = 0,j=result.length-1; i < arrays.length; i++,j--) {
result[j] = arrays[i];
}
return result;
}
4.多维数组
-
多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组。
-
二维数组
int a[][] = new int[2][5];
-
解析:a=两行,五列
5.Arrays类
- 数组的工具类java.util.Arrays(查看jdk帮助文档)
6.稀疏数组
-
当一个数组中大部分元素为0,或者为同一值的数组时,可以使用稀疏数组来保存改数组。
-
稀疏数组的处理方式是:
记录数组一共都有几行几列,有多少个不同值
把具有不同值的元素和行列及值记录在一个小规模的数组中,从而缩小程序的规模
public static void main(String[] args) {
//1.创建一个二位数组 11*11 0:没有棋子 , 1:黑棋 2:白棋
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][3] = 2;
//输出原始数组
System.out.println("输出原始的数组");
for (int[] ints : array1) {
for (int anInt:ints) {
System.out.print(anInt+"\t");
}
System.out.println();
}
System.out.println("===================================");
//转换为稀疏数组保存
//或获取有效值的个数
int sum=0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if (array1[i][j]!=0){
sum++;
}
}
}
System.out.println("有效值的个数:"+sum);
//2.创建一个稀疏数组的数组
int[][] array2 = new int[sum+1][3];
array2[0][0] = 11;
array2[0][1] = 11;
array2[0][2] = sum;
//遍历二维数组,将非零的值,存放在稀疏数组中
int count = 0;
for (int i = 0; i < array1.length; i++) {
for (int j = 0; j < array1[i].length; j++) {
if (array1[i][j]!=0){
count++;
array2[count][0] = i;
array2[count][1] = j;
array2[count][2] =array1[i][j];
}
}
}
System.out.println("稀疏数组");
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i][0] + "\t"
+ array2[i][1] + "\t"
+ array2[i][2] + "\t");
}
System.out.println("===========================");
System.out.println("还原稀疏数组");
//1.读取稀疏数组
int[][] array3 = new int[array2[0][0]][array2[0][1]];
//2.给其中的元素还原它的值
for (int i = 1; i < array2.length; i++) {
array3[array2[i][0]][array2[i][1]] = array2[i][2];
}
//打印
System.out.println("输出还原的数组");
for (int[] ints : array3) {
for (int anInt:ints) {
System.out.print(anInt+"\t");
}
System.out.println();
}
}
7.冒泡排序
public static void main(String[] args) {
//嵌套循环,时间复杂度为0(n2)
int[] a = {12,234,6543,7,43,34};
int[] sort = sort(a);
System.out.println(sort);
//输出数组的三种方式
for (int i = 0; i < sort.length; i++) {
System.out.println(sort[i]);
}
//利用Array类中的toString方法
System.out.println(Arrays.toString(sort));
for ( int test:sort) {
System.out.println(test);
}
}
//冒泡排序
//1.比较数组中,两个相邻的元素,如果第一个数比二个数大,就交换的他们的位置
//2.每一次比较,都会产生出一个最大,或者最小的数字
//3.下一轮则可以少一次排序
//4.依次循坏,知道结束
public static int[] sort(int[] array){
//临时变量
int temp = 0;
//外层循环,判断我们这个要走多少次;
for (int i = 0; i < array.length-1; i++) {
boolean flag = false;//通过flag标识位减少没有意义的比较
//内层循坏,比较判断两个数,如果一个数,比第二数大,则交换位置
for (int j = 0; j< array.length-1-i ; j++) {
if (array[j+1]>array[j]){
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
flag = true;
}
}
if (flag==false){
break;
}
}
return array;
}
}
java内存分析
java内存
- 堆
- 存放new的对象和数组
- 可以被所有的线程共享,不会存放别的对象引用
- 栈
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里面的具体地址)
- 方法区
- 可以被所有的线程共享
- 包含了所有的class和static变量
面向对象
初识面向对象
面向过程思想:第一步做什么,第二步做什么…;面向过程合适处理一些较为简单的问题
面向对象思想:物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。适合处理复杂的问题
面向的对象的本质就是:以类的方式组织代码,以对象的组织(封装)数据。
抽象
三大特性:封装;继承;多态
对象的创建分析
使用new关键字创建对象:使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中的构造器的调用。
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的,并且构造器有以下两个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
构造器的作用:使用new关键字,本质是在调用构造器;用来初始化值
/*
1.类与对象
类是一个模板:抽象,对象是一个具体的实列
2.方法
方法的定义和调用
3.对应的引用
引用类型 基本类型(8)
对象是通过引用类操作的:栈----->堆
4.属性:字段Field 成员变量
默认初始化:
数字:0 0.0
char:u0000
boolean:false
引用:null
修饰符 属性类型 属性名 = 属性值!
5.对象的创建和使用
-必循使用new 关键字创建对象,构造器 Person yxt = new Person();
-对象的属性:yxt.name
-对象方法:yxt.sleep()
6.类:
静态的属性--->属性
动态的行为--->方法
*/
面向对象三大特征
封装
该露的露,该藏的藏
我们的程序设计要追求==“高内聚,低耦合”==。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用
封装(数据的隐藏)
通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来分访问,这称为信息隐藏。
属性私有,get/set
意义:
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 系统可维护性增加了
继承
概述
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
extends的意思是“扩展”。子类是父类的扩展。
Java类中只有单继承,没有多继承。
继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。
继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。
子类和父类之间,从意义上讲应该具有“is a” 的关系。
Object类
Java中,所有的类,都默认直接或者间接继承Object
super---->父类 this----->当前的
super注意点:
- super调用父类的构造方法,必须在构造方法的第一个
- super必须只能在出现在子类的方法或者构造方法中!
- super和this不能同时调用构造方法!
this注意点:
- this:本身调用者这个对象
- super:代表父类对象的应用
前提:
this:没有继承也可以使用
super:只能在继承条件才可以使用
构造方法:
this();本类的构造
super();父类的构造!
方法重写
重写都是方法的重写,和属性无关
//在java中,所有的类都默认直接或间接继承Object
//人:父类
public class Person {
public void test(){
System.out.println("父类方法");
}
//学生 is 人 派生类 子类
//子类继承父类,就会拥有父类的全部方法
public class Student extends Person {
// override-->重写
@Override
public void test() {
System.out.println("子类方法");
}
}
//静态方法和非静态的方法区别很大!
//静态方法:方法的调用只和左边定义的数据类型有关
//非静态:重写
public static void main(String[] args) {
Student student = new Student();
student.test();
//父类的引用指向了子类
Person person = new Student();
person.test();
}
}
总结:需要有继承关系,子类重写父类的方法!;
- 方法名必须相同
- 参数列表必须相同
- 修饰符:范围可以扩大但不能缩小:public>Protected>Default>private
- 抛出的异常:范围可以缩小,但不能扩大;ClassNotFoundException---->Exception(大)
重写,子类的方法和父类必要一致;方法体不同
为什么需要重写:
- 父类的功能,子类不一定需要或者不一定满足!
多态
概述
- 同一方法可以根据发送对象的不同而采用多种不同的行为方式
- 一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多
多态存在条件
- 有继承关系
- 子类重写父类方法
- 父类引用指向子类对象
//人:父类
public class Person {
public void run(){
System.out.println("父类方法run");
}
//学生 is 人 派生类 子类
//子类继承父类,就会拥有父类的全部方法
public class Student extends Person {
@Override
public void run() {
System.out.println("子类方法run");
}
public void eat(){
System.out.println("子类方法eat");
}
}
public static void main(String[] args) {
//一个对象的实际类型是确定的;可以指向的引用类型就不确定了
//子类 能调用的方法都是自己的或者继承父类的
Student s1 = new Student();
//父类,可以指向子类但是不能调用子类独有的方法
Person s2 = new Student();
Object s3 = new Student();
//对象能执行哪些方法,主要看对象左边的类型,和右边关系不大!
s2.run(); //子类重写父类的方法,执行子类的方法
s1.run();
s1.eat();
}
多态注意事项:
- 多态是方法的多态,属性没有多态性。
- 父类和子类,有联系 类型转换异常! ClassCastException!
- 存在条件:继承关系,方法需要重写,父类引用指向子类对象!
不能被重写:
-
static 方法属于类,不属于实例
-
final 常量
-
private方法
instanceof
(类型转换)引用类型,判断一个对象是什么类型
//人:父类
public class Person {
public void run(){
System.out.println("父类方法run");
}
//学生 is 人 派生类 子类
//子类继承父类,就会拥有父类的全部方法
public class Student extends Person {}
public static void main(String[] args) {
//Object > String
//Object > Person > Teacher
//Object > Person > Student
Object s1 = new Student();
System.out.println(s1 instanceof Student);//true
System.out.println(s1 instanceof Person);//true
System.out.println(s1 instanceof Object);//true
System.out.println(s1 instanceof Teacher);//false
System.out.println(s1 instanceof String);//false
//Object > Person > Teacher
//Object > Person > Student
Person s2 = new Student();
System.out.println(s2 instanceof Student);//true
System.out.println(s2 instanceof Person);//true
System.out.println(s2 instanceof Object);//true
System.out.println(s2 instanceof Teacher);//false
//System.out.println(s2 instanceof String);//编译就报错!
}
static关键字
public class Person {
{
System.out.println("匿名代码块");//第二个执行
}
static {
System.out.println("静态代码块");//第一个执行(只执行一次)
}
public Person(){
System.out.println("构造方法");//第三个执行
}
public static void main(String[] args) {
Person person = new Person();
System.out.println("=====================================");
Person person1 = new Person();
}
}
//静态导入包
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class startTest {
public static void main(String[] args) {
System.out.println(random());
System.out.println(PI);
}
}
抽象类和接口
抽象类
-
abstratct修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类;那么该类就是抽象类。
-
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
-
抽象类,不能使用new关键字来创建对象,它是用来让子类继承,去实现的。
-
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
-
子类就成抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。
-
抽象类中可以写普通方法
-
抽象方法必须在抽象类中
//抽象类
public abstract class abstratctDemo {
//抽象方法
public abstract void run();
}
//抽象类的所有方法,继承了它的子类,都必须要实现它的方法----除非子类也是抽象方法
public class test extends abstratctDemo{
@Override
public void run() {
}
}
接口
概述
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是…则必须能…”思想。如果你是天使,则必须能飞。如果你是汽车则必须能跑。
- 接口的本质是契约,就像我们人间的法律一样。制定好后大家都遵守。
- 00的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如c++、java、c#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象。
- 声明接口的关键字是interface
- 接口不能被实例化,接口中没有构造方法
- implements可以实现多个接口
//interface 定义的关键字,接口都需要有实现类
public interface Demo {
//常量 public static final
int AGE = 99;
//接口中所有定义的方法其实都是抽象的 public abstract
void add(String name);
void delete(String name);
}
//实现了接口的类,就需要重写接口中的方法
public class demoImpl implements Demo{
@Override
public void add(String name) {
}
@Override
public void delete(String name) {
}
}
内部类及OOP实战
概述
内部类就是在一个类的内部再定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。
- 成员内部类
public class outer {
private int id=10;
public void out(){
System.out.println("这是外部类的方法");
}
public class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
//获得外部类的私有属性
public void getID(){
System.out.println(id);
}
}
}
public class Test {
public static void main(String[] args) {
outer outer = new outer();
//通过外部类来实例化内部类
outer.Inner inner = outer.new Inner();
inner.getID();
}
}
- 静态内部类
public class outer {
private int id=10;
public void out(){
System.out.println("这是外部类的方法");
}
public static class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
}
}
- 局部内部类
public class outer {
//局部内部类
public void method(){
class Inner{
public void in(){
System.out.println("局部内部类");
}
}
}
}
- 匿名内部类
public class Test {
public static void main(String[] args) {
//没有名字初始化类,不用讲实例保存到变量中
new Apple().eat();
user user = new user() {
@Override
public void hello() {
}
};
}
}
class Apple{
public void eat(){
System.out.println("eat");
}
}
interface user{
void hello();
}
异常机制
什么是异常
- 软件程序在在运行过程中,非常可能遇到问题,我们叫异常,英文是:Exception,意思是例外
- 异常是指程序运行中出现的不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。影响了正常的程序执行流程
分类
- 检查性异常:用户错误或者其他问题引起的异常,这些是程序员无法预见的
- 运行时异常:运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略
- 错误ERROR:错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
异常体系结构
- java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类
- 在Java API中已经定义了许多异常类,这些异常分为两大类,错误Error和异常Exception
Error和Exception的区别
Error通常是灾难性的致命的错误,是程序员无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
异常处理机制
-
抛出异常
public static void main(String[] args) { try { new test().test(1,0); } catch (ArithmeticException e) { e.printStackTrace(); } } public void test(int a,int b) throws ArithmeticException{//在方法上抛出异常 if (b==0){ throw new ArithmeticException();//主动抛出的异常,一般在方法中使用 } } }
-
捕获异常
public class test {
public static void main(String[] args) {
int a = 1;
int b = 0;
try{//监控区域
System.out.println(a/b);
}catch (ArithmeticException e){//catch(想要捕获的异常类型)
System.out.println("程序出项错误");
}finally {//善后处理工作
System.out.println("finally");
}
}
}
-
异常处理五个关键字
try、catch、finally、throw、throws
自定义异常
- 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。
- 在程序中使用自定义异常类,大体可分为以下几个步骤:
1.创建自定义异常类。
2.在方法中通过throw关键字抛出异常对象。
3.如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
4.在出现异常方法的调用者中捕获并处理异常。
public class test {
static void test(int a) throws MyException {
System.out.println("传递的参数为:"+a);
if (a>10){
throw new MyException(a);
}
System.out.println("ok");
}
public static void main(String[] args) {
try {
test(11);
} catch (MyException e) {
//可以增加一些处理异常的代码块
System.out.println("MyException"+e);
}
}
}
//自定义异常
public class MyException extends Exception{
//传递数字>10就抛出异常
private int detail;
public MyException(int a){
this.detail=a;
}
//toString:异常的打印信息
@Override
public String toString() {
return "MyException{" +
"detail=" + detail +
'}';
}
}
实际应用中的经验总结:
- 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
- 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
- 对于不确定的代码,也可以加上try-catch,处理潜在的异常
- 尽量去处理异常,切忌只是简单地调用 printStackTrace ()去打印输出
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定
- 尽量添加finally语句块去释放占用的资源
常用类
- Object类
- Math类------常见的数学运算
- Sting类------不可变final(操作量较少的时候建议)
- StringBuffer------可变长(多线程数据量较大,效率低,安全)
- StringBuilder------可变长(单线程数据量较大,效率高,不安全)
- Random类-------生成随机数 UUID
- File类
- 创建文件
- 产看文件
- 修改文件
- 删除文件
- Date类
- Date
- SimpleDateFormat(yyyy-MM-dd HH:mm:ss)
- Calendar(建议使用)
- 包装类------自动装箱和拆箱
集合框架
集合概念
- 概念:对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能。
- 和数组的区别:
- 数组长度固定,集合长度不固定
- 数组可以存储基本类型和引用类型,集合只能存储引用类型
- 位置:java.util.*;
Collection接口
List接口与实现类
泛型和工具类
Set接口与实现类
I/O详解
多线程详解
线程简介
Process与Thread
- 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
- 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位。
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错局。
概念
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制:
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互/内存控制不当会造成数据不一致
线程实现
三种创建方式
Thread calss ------->继承Thread(重点)
继承Thread类
子类继承Thread类具备多线程能力?
启动线程:子类对象. start/
不建议使用:避免OOP单继承局限性
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
public class TestThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----"+i);
}
}
//mian线程
public static void main(String[] args) {
//创建一个线程对象
TestThread testThread = new TestThread();
//调用start()方法开启线程
testThread.start();//同时执行
//testThread.run();//先执行run,后执行main
for (int i = 0; i < 200; i++) {
System.out.println("我在学习线程"+i);
}
}
}
Runnable接口-------> 实现Runnable接口(重点)
实现Runnable接口
实现接口Runnable具有多线程能力
启动线程:传入目标对象+Thread对象. start/)
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
//创建线程方式二:实现runnable接口,重写run()方法,执行线程需要丢入runnable接口实现类,调用start开启线程
public class TestThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----" + i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
TestThread testThread = new TestThread();
//创建线程对象,通过线程对象来开启我们的线程,代理
// Thread thread = new Thread(testThread);
// thread.start();
new Thread(testThread).start();
for (int i = 0; i < 500; i++) {
System.out.println("我在学习多线程--"+i);
}
}
}
//模拟龟兔赛跑
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子休息
if (Thread.currentThread().getName().equals("兔子") && i%10==0){
try{
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
//如果比赛结束了,就停止程序
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
}
}
//判断是否完成比赛
private boolean gameOver(int steps){
//判断是否有胜利者
if (winner!=null){//已经存在胜利者了
return true;
}{
if (steps>=100){
winner=Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
Callable接口--------->实现Callable接口(了解)
有返回值类型
重写call方法,需要抛出异常
静态代理
//静态代理模式总结
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色
//好处:
//代理对象可以做很多真实对象做不了事情
//真实对象专注做自己的事情
public class StacticProxy {
public static void main(String[] args) {
new Thread(()-> System.out.println("我爱你")).start();
new WeddingCompany(new You()).HappMarry();
}
}
interface Marry{
void HappMarry();
}
//真实角色,你去结婚
class You implements Marry{
@Override
public void HappMarry() {
System.out.println("你要结婚了,开心");
}
}
//代理角色,帮助你结婚
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target){
this.target = target;
}
@Override
public void HappMarry() {
after();
this.target.HappMarry();//真实对象
before();
}
private void after() {
System.out.println("结婚之前布置现场");
}
private void before() {
System.out.println("结婚之后,收尾款");
}
}
线程状态
线程休眠
//模拟倒计时
public class Demo {
public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<=0){
break;
}
}
}
}
每个对象都有一个锁,sleep不会释放锁。
线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功!看cpu心情。
//模拟礼让
public class Demo {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
Join
- Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。
//模拟Jion方法
public class Demo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程VIP来了"+i);
}
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
//启动线程
Thread thread = new Thread(demo);
thread.start();
//主线程
for (int i = 0; i < 1000; i++) {
if (i==200){
thread.join();//插队
}
System.out.println("主线程"+i);
}
}
}
观测线程状态
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("!!");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);//NEW
//观察启动后
thread.start();
state = thread.getState();
System.out.println(state);
while(state !=Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
Thread.sleep(1000);
state = thread.getState();//跟新线程状态
System.out.println(state);//输出状态
}
}
}
线程优先级
-
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
-
线程的优先级用数字表示,范围从1~10
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5; ----->默认值是5
-
使用以下方式获取优先级
getPriority().setPriority(int xxx)
//测试线程的优先级
public class Demo {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"主线程--->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread thread = new Thread(myPriority);
Thread thread1 = new Thread(myPriority);
Thread thread2 = new Thread(myPriority);
Thread thread3 = new Thread(myPriority);
Thread thread4 = new Thread(myPriority);
Thread thread5 = new Thread(myPriority);
//设置优先级,再启动
thread.start();
thread1.start();
thread2.setPriority(1);
thread2.start();
thread3.setPriority(4);
thread3.start();
thread4.setPriority(10);
thread4.start();
thread5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
}
}
注意:优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度。
守护(daemon)线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如:后台记录操作日志,监控内存,垃圾回收等等
//测试守护线程
public class Demo {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);//默认是false表示是用户线程,正常的线程都是用户线程。。。
thread.start();//守护线程启动
new Thread(you).start();//用户线程启动。。。
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("守护神");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 30000; i++) {
System.out.println("一直开心的活着");
}
System.out.println("====goodbye!word!======");
}
}
线程同步
并发
- 同一个对象被多个线程同时操作
- 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此多想的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
队列和锁
- 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可.存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题.
三大不安全案例
//不安全的买票
public class Demo {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"学生").start();
new Thread(buyTicket,"老师").start();
new Thread(buyTicket,"黄牛党").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
//判断是否有票
if (ticketNums<=0){
return;
}
//模拟延时
Thread.sleep(1000);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100, "基金");
Drawing you = new Drawing(account, 50, "你");
Drawing you1 = new Drawing(account, 100, "他**");
you.start();
you1.start();
}
}
//账户
class Account{
int money;//余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行
class Drawing extends Thread{
Account account;//账户
int drawingMoney;//取了多少钱
int nowMoney;//现在有多少钱
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//判断有没有钱
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"没有钱,取不了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 你取的钱
account.money = account.money - drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
// Thread.currentThread().getName() = this.getName();
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
//线程不安全集合
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
strings.add(Thread.currentThread().getName());
}).start();
}
System.out.println(strings.size());
}
}
同步方法
- 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法: synchronized 方法和 synchronized 块.
同步方法:public synchronized void method(int args){}
- synchronized 方法控制对“对象”的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得得这个锁,继续执行
缺陷;若将一个大的方法申明为 synchronized 将会影响效率
public class Demo {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"学生").start();
new Thread(buyTicket,"老师").start();
new Thread(buyTicket,"黄牛党").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized 同步方法,锁的是this
private synchronized void buy() throws InterruptedException {
//判断是否有票
if (ticketNums<=0){
return;
}
//模拟延时
Thread.sleep(1000);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
- 锁的对象就是变化的量,需要的增删改的对象
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(1000, "基金");
Drawing you = new Drawing(account, 50, "你");
Drawing you1 = new Drawing(account, 100, "他**");
you.start();
you1.start();
}
}
//账户
class Account{
int money;//余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行
class Drawing extends Thread{
Account account;//账户
int drawingMoney;//取了多少钱
int nowMoney;//现在有多少钱
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
//synchronized 默认锁的是this
@Override
public void run() {
synchronized (account){
//判断有没有钱
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"没有钱,取不了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 你取的钱
account.money = account.money - drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
// Thread.currentThread().getName() = this.getName();
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
}
//线程不安全集合
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
ArrayList<String> strings = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (strings){
strings.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(1000);
System.out.println(strings.size());
}
}
同步块
- 同步块:synchronized(Obj){}
- Obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法中的同步监视器就是this,就是这个对象本身,或者是class
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发次同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
//测试JUC安全类型的集合
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
死锁
-
多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”,就可能会发生”死锁“的问题。
-
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
想办法破其中的任意一个或多个条件就可以避免死锁发生
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"灰姑凉");
Makeup g2 = new Makeup(0,"灰姑凉");
new Thread(g1).start();
new Thread(g2).start();
}
}
//口红
class Lipstick{}
//镜子
class Mirror{}
class Makeup extends Thread{
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice; //选择
String grilName;
Makeup(int choice, String grilName) {
this.choice =choice;
this.grilName = grilName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.grilName+"获得口红的锁");
Thread.sleep(1000);
System.out.println(this.mirror+"获得镜子的锁");
}
}else{
System.out.println(this.mirror+"获得镜子的锁");
Thread.sleep(1000);
System.out.println(this.grilName+"获得口红的锁");
}
}
}
Lock锁
- 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
- java. util. concurrent :llocks:tock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock 类实现了Lock,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock ,可以显式加锁、释放锁。
public class TestLock {
public static void main(String[] args) {
Lock2 lock2 = new Lock2();
new Thread(lock2).start();
new Thread(lock2).start();
new Thread(lock2).start();
}
}
class Lock2 implements Runnable{
int ticketNums = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();//加锁
if (ticketNums > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
} else {
break;
}
} finally {
//解锁
lock.unlock();
}
}
}
}
synchronized与lockd的对比
-
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized 是隐式锁,出了作用域自动释放
-
Lock只有代码块锁_ synchronized 有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)—
优先使用顺序:book>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
线程通信问题
生产者消费者模式
- 应用场景:生产者和消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费.
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止,
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止.
- 这是一个线程同步问题(生产者和消费者共享同个资源,并且生产者和消费者之间相互依赖,互为条件.
- 对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后,又需要马上通知消费者消费
- 对于消费者, 在消费之后,要通知生产者已经结束消费, 需要生产新的产品以供消费.
- 在生产者消费者问题中,仅有synchronized是不够的
synchronized可阻止并发更新同一一个共享资源,实现了同步
sVnchronized 不能用来实现不同线程之间的消息传涕(通信)
- Java提供了几个方法解决线程之间的通信问题
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long timeout)notify() | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象.上所有调用wait()方法的线程, 优先级别高的线程优先调度 |
注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常llegalMonitorStateException
- 解决方式1
- 并发协作模型“生产者1消费者模式”–>管程法
生产者:负责生产数据的模块(可能是方法, 对象,线程,进程);
消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
- 并发协作模型“生产者1消费者模式”–>管程法
- 解决方式2
- 并发协作模型”生产者/消费者模式“—>信号灯法