数组初始化
数组静态初始化:
int[] arr = new int[]{1,2,3};
//简化版
int[] arr = {1,2,3};
数组动态初始化:
int[] arr=new int[3]{0};
数组操作的两个常见小问题
索引越界 -报错信息:ArrayIndexOutOfBoundsException
空指针异常 -报错信息:NullPointerException
方法重载
多个方法在同一个类中
多个方法具有相同的方法名
多个方法的参数不相同,类型不同或者数量不同(仅参数的类型不同也可以构成方法重载,但与函数返回值的类型无关)
public class MethodDemo{
pubilc static void fn(int a){
//方法体
pubilc static int fn(int a){
//方法体
}//仅返回值不同,不构成方法重载
}
}
堆栈
new出来的东西在堆内存
Phone p = new Phone();
类和对象
类——对象的抽象,对象——类的实体
类
概念:类是java程序的基本组成单位,是对现实生活中具有共同属性和行为的事务的抽象,确定对象将会拥有的属性和行为。
类的组成:属性和行为
属性:成员变量(类中方法外的变量)
行为:成员方法(和前面的方法相比去掉static关键字即可)
pubilc class 类名{
//成员变量
变量1的数据类型 变量1;
变量2的数据类型 变量2;
…
//成员方法
方法1;
方法2;
…
}
对象的使用
pubilc class PhoneDemo{
pubilc static void main(String[] args){
//创建一个对象
Phone p = new Phone(); //new出来的p在堆内存
System.out.println(p.brand);//输出默认值 String默认值为null
p.call();
p.sendMessage();//使用成员方法
}
}
对象内存图
单个对象
pubilc class StudentTest01{
pubilc static void main(String[] args){
//程序的执行是从main方法开始的,所以main方法在栈内存
Student s = new Student();
//s在栈内存,地址为001。new Student()在堆内存地址也为001
System.out.println(s);
//study方法在栈内存,当s调用study方法执行完成后,study方法出栈
s.study();
s.doHomework();
}
}
多个对象

成员变量和局部变量
成员变量:类中方法外的变量
局部变量:方法中的变量(包括形参上的变量)

private关键字
//学生类
public class Student{
String name;
private int age;//通过设置setAge、getAge来获取设置age字段。s.setAge(20);
//提供get/set方法
public void setAge(int a){
//设置字段规则
if(a < 0 || a > 120){
System.out.println("error");
}else age = a;
}
pubilc int getAge(){
return age;
}
}
一个标准的类
public class Student{
private String name;
private int age;
//get/set方法
public int getName() {
return name;
}
public void setName(int name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
封装
封装概述:面向对象的三大特征(封装、继承、多态)
封装原则:
将类的某些信息隐藏在内部,不允许外部程序直接访问,而是通过类提供的方法对隐藏信息的操作和访问成员变量 private ,提供对应的 getXxx() / setXxx() 方法。
封装好处:
通过方法来控制成员变量的操作,提高了代码的 安全性 。
把代码用方法进行封装,提高了代码的 复用性 。
构造方法
建议书写格式:无论是否使用,都写一个无参数构造的方法。
标准类制作:
快捷键: option + command + v
public class Student{
private String name;
private int age;
public Student(){}//构造无参数方法
public Student(String name ,int age){//构造带参数方法
this.age = age;
this.name = name;
}
public void setName(String name){ //set/get方法
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge(){
return age;
}
public void show(){ //show方法
System.out.println(name + "," + age);
}
}
API(Application Programming Interface)应用程序编程接口
Java中的API:J DK中提供的实现各种功能的Java类
查询Random ——java.util包 (java.lang不需要导包)
scanner类
键盘输入
import java.awt.font.ShapeGraphicAttribute;
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
//创建对象
Scanner sc = new Scanner(System.in);
//接收数据
System.out.println("please input the string:");
String line = sc.nextLine();
//输出结果
System.out.println(line);
}
}
调用方法的时候,如果方法有明确的返回值,我们用变量接收
(快捷键option+command+v)
String构造方法
1.第一种方式:在堆内存开辟常量池
String s1 = "abc"; //JVM会建立一个String对象放在字符串池中,并给s1参考
String s2 = "abc"; //s2会直接参考字符串池中的String对象,本质上是同一个对象
System.out.println(s1==s2);//比较地址 true
2.第二种方式:new String() 放在堆内存
char[] chs = {"a","b","c"}
String s1 = new String(chs);
String s2 = new String(chs);
System.out.println(s1==s2);//比较地址 false
3.比较字符串是否相同
System.out.println(s1。equals(s2); //true
String和StringBuilder
String:内容是不可变的
StringBuilder:内容是可变的
StringBuilder构造方法
public class Main {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
System.out.println("sb:" + sb); //sb:
StringBuilder sb2 = new StringBuilder("hello");
System.out.println("sb:" + sb2); //sb:hello
//添加方法
sb.append("world").append("java").append("!");
}
}
```java
public class Main {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("hello");
//通过toString()就可以实现吧StringBuild转换为String
String s=sb.toString();
System.out.println("s:" + s);
//String 转换为StringBuilder
String s1 = "hello";
StringBuilder sb1 = new StringBuilder(s1);
}
}
集合基础
ArrayList<>
public class Main {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("hellO");
array.add("world");
array.add(1,"javase"); //hello javase world
array.add(4,"1234"); //IndexOutOfBoundsException 越界异常
}
}
常用的方法
array.remove()
array.remove("Awin");//元素不存在时,不会产生越界异常
array.remove(3); //索引不存在时,会产生越界异常
array.set()
array.set(1,"Awin");//与add不同,替换1处的元素
array.get()------------------------复习:String里面用charAt(i)定位
array.size()
利用array遍历学生信息
public class Main {
public static void main(String[] args) {
ArrayList<Student> array = new ArrayList<Student>();
Student s1 = new Student("Awin",30);
Student s2 = new Student("Kevin",18);
array.add(s1);
array.add(s2);
for (int i = 0; i < array.size(); i++) {
Student s = array.get(i);
System.out.println(s.getName() + "," +s.getAge());
}
}
}
利用array从键盘获取学生信息
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
ArrayList<Student> array = new ArrayList<>();
addStudent(array);
addStudent(array);
for (int i = 0; i < array.size(); i++) {
Student s = array.get(i);
System.out.println(s.getName()+","+s.getAge());
}
}
//封装成方法
public static void addStudent(ArrayList<Student> array){
Scanner sc = new Scanner(System.in);
System.out.println("input name");
System.out.println("input age");
String name = sc.nextLine();
String age = sc.nextLine();
Student s = new Student();
s.setName(name);
s.setAge(age);
array.add(s);
}
}
继承
继承的格式:pubilc class 子类名 extends 父类名{}
父类也被称为基类、超类
子类也被称为派生类
什么时候才用继承?
假设法:有两个类A和B,如果他们满足A是B的一种,或者B是A 的一种,就说明他们存在继承关系。
super
this和super

继承中构造方法中的访问特点
子类中所有的构造方法默认都会执行父类中无参的构造方法
因为子类会继承父类中的数据,可能还会使用父类中的数据。所以,子类初始化之前,一定要先完成父类数据的初始化。
每一个子类构造方法的第一条语句默认都是:super()。——可能被隐藏
如果父类中没有无参构造方法,只有带参构造方法,该怎么办?
通过在子类使用super关键字去显示的调用父类的带参构造方法
方法重写
@override
权限:public > protected > default > private( public > 默认 > 私有 )
父类的私有成员子类是不能继承的
子类方法的访问权限不能更低
Java中继承的注意事项:
Java中类只支持单继承,不支持多继承
Java中类支持多层继承
包的概述和使用
作用:对类进行分类管理
包的定义格式:package
导包:import 简化书写格式
权限修饰符
修饰符 | 同一个类中 | 同一个包中 | 不同包的子类 | 不同包的无关类 |
private | 是 | |||
默认 | 是 | 是 | ||
protected | 是 | 是 | 是 | |
public | 是 | 是 | 是 | 是 |
final修饰符
被final修饰的方法,为最终方法:不可被重写
被final修饰的成员变量,为常量:不可被重新赋值
被final修饰的类,为最终类:不可作父类,不可被继承
final 修饰引用类型变量:引用类型的地址值不能改变,数据值可以改变。
static修饰符
static 成员变量、成员方法
static修饰的特点
被类的所有对象访问
这也是我们判断是否使用静态关键字的条件
可以通过类名调用
也可以使用对象名调用(Student.university="Hunnu")
静态的成员方法:只能访问静态成员
多态
多态的前提和体现:
有继承/实现关系
有方法重写
有父类引用指向子类对象
public class AnimalDemo {
public static void main(String[] args) {
//有父类引用指向子类对象
Animal mimi = new Cat();
mimi.eat();
}
}
多态中成员访问特点
成员变量:编译看左边,执行看左边
成员方法:编译看左边,执行看右边
为什么成员变量和成员方法的访问不一样呢?
因为成员方法有重写,成员变量没有
多态的好处和弊端:
多态的好处:提高了程序的扩展性
具体表现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体子类型参与操作。
AnimalOperator a = new AnimalOperator();
Cat c = new Cat();
a.useAnimal(c);
多态的弊端:不能使用子类的特有功能
只能使用子类中重写的方法。
多态中的转型
向上转型、向下转型
public class AnimalDemo {
public static void main(String[] args) {
//有父类引用指向子类对象
Animal a = new Cat();//向上转型
a.eat();
// a.playGame();父类中编译,没有playGame方法
//向下转型
Cat c = (Cat)a;
c.playGame();
}
}
抽象类的特点
抽象类和抽象方法必须使用abstact关键字修饰
public abstract class Dog(){}
public abstract void eat();
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
抽象类不能实例化
抽象类实例化:参照多态,通过子类对象实例化,为抽象类多态
抽象类的子类
要么重写抽象类中所有的抽象方法
要么本身也是抽象类
接口
接口概述
Java中的接口更多的体现在对行为的抽象
成员变量
只能是常量
默认修饰符:public static final
构造方法
接口没有构造方法,因为接口是对行为进行抽象的,没有具体存在
一个类如果没有父类,默认继承自Object类
成员方法
只能是抽象方法
默认修饰符:public abstract
关于接口的方法,JDK8和9有一些新特性。
接口的使用
public class AnimalDemo {
public static void main(String[] args) {
Jummping j = new Cat();
j.jump();//cat can jump
Animal a = new Cat();
a.setName("Mimi");
a.setAge(18);
System.out.println(a.getName()+","+a.getAge());
a.eat();//cat eat fish
//带参数构造
a = new Cat("Mimi",5);
System.out.println(a.getName()+","+a.getAge());
a.eat();//cat eat fish
}
}
类和接口的关系
类和类的关系
继承关系,只能单继承,但是可以多层继承
类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
接口和接口的关系
继承关系,可以单继承,也可以多继承
抽象类和接口的区别
成员区别
抽象类 | 变量、常量;有构造方法;有抽象方法和非抽象方法 |
接口 | 常量;只有抽象方法 |
关系区别
类与类 | 继承,只能单继承 |
类与接口 | 实现,可以单实现,也可以多实现 |
接口与接口 | 继承,单继承,多继承 |
形参和返回值
同C
内部类
成员内部类:
局部内部类:在方法中定义的类,外界无法直接使用,需要在方法内部创建对象并使用
匿名内部类:存在一个类或者接口,这里的类可以是具体类,也可以是抽象类
本质:继承了该类或者实现了该接口的子类的匿名对象
System类常用方法
System.currentTimeMillis();
System.exit(0);
Object
Object是类层次结构的根,每个类都可以将Object作为超类。所有类直接或间接的继承自该类
构造方法:public Object()

Arrays类
Arrays.sort()
int[] arr = {1,2,3,4,5,6};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
Arrays是一个工具类,工具类的设计思想:
构造方法用private修饰
成员用public static修饰
基本类型包装类
将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据常用的操作之一:用于基本数据类型与字符串之间的转换
自动装箱和拆箱
//装箱
Integer i = 100;
//拆箱
i = i.intValue() +200;
//自动装拆
i +=100;
SimpleDateFormat
public class Date1 {
public static void main(String[] args) {
//Date->String
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String s = sdf.format(d).toString();
System.out.println(s);
//String->Date
String ss = "2048-08-09 11:11:11";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date dd = null;
try {
dd = sdf2.parse(ss);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(dd);
}
}
。。。。
实例日期工具类
//Date1 封装工具类
public class Date1 {
private Date1(){}
public static String dateToString(Date date , String format){
SimpleDateFormat sdf = new SimpleDateFormat(format);
String s = sdf.format(date);
return s;
}
public static Date stringDate(String s,String format) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(format);
Date d = sdf.parse(s);
return d;
}
}
//DateDemo测试类
public class DateDemo {
public static void main(String[] args) throws ParseException {
//创建日期对象
Date d = new Date();
String s1 = Date1.dateToString(d,"yyyy年MM月dd日 HH:mm:ss");
System.out.println(s1);
String s2 ="2048-08-09 12:12:12";
Date date = Date1.stringDate(s2,"yyyy-MM-dd HH:mm:ss");
System.out.println(date);
}
}
Calendar类
使用
public class CalendarDemo {
public static void main(String[] args) {
Calendar c = Calendar.getInstance();//多态的形式
System.out.println(c);
int i = c.get(Calendar.YEAR);
int i1 = c.get(Calendar.MONTH) + 1;
int i2 = c.get(Calendar.DATE);
System.out.println(i+i1+i2);
}
}
二月天案例
public class CalendarDemo {
public static void main(String[] args) {
Scanner sc =new Scanner(System.in);
System.out.println("please input year:");
int year = sc.nextInt();
Calendar c = Calendar.getInstance();//多态的形式
c.set(year,2,1);//2为 三月
c.add(Calendar.DATE,-1);//三月一号的前一天
int date = c.get(Calendar.DATE);//获取
System.out.println(year+"s's Feb is "+date +"days");
}
}
异常

JVM的默认处理方案
如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理
把异常的名称、异常原因及异常出现的位置等信息输出在了控制台
程序停止执行
异常处理
try...catch...
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("Start");
method();
System.out.println("end");
}
private static void method() {
try {
int[] arr = {1,2,3};
System.out.println(arr[3]);
}catch (ArrayIndexOutOfBoundsException e){
// System.out.println("ArrayIndexOutOfBoundsException");
e.printStackTrace();
}
}
}
Throwable三种方法
编译时异常:必须显示处理,否则程序无法运行
自定义异常:
public class Teacher {
public void checkScore(int score) throws ScoreException{
if(score < 0 || score > 100){
// throw new ScoreException();
throw new ScoreException("the score error");
}else{
System.out.println("分数正常");
}
}
}
public class TeacherDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("input score");
int score = sc.nextInt();
Teacher t = new Teacher();
try {
t.checkScore(score);
} catch (ScoreException e) {
e.printStackTrace();
}
}
public class ScoreException extends Exception{
public ScoreException(){}
public ScoreException(String message){
super(message);
}
}
throws和throw的区别
throws
用在方法声明后面,跟的是异常类名
表示抛出异常,由该方法的调用者来处理
表示出现异常的一种可能性,并不一定会发生这些异常
throw
用在方法体内,跟的是异常对象名
表示抛出异常,由方法体内的语句处理。
集合
集合类体系结构:单列集合( Collection )和双列集合( Map )
单列集合分为:List(可重复) 和 Set(不可重复)
List :ArrayList、LinkedList、……
Set :HashSet、TreeSet、……
Map :HashMap、……
Collection集合的遍历
Iterator:迭代器,集合的专用遍历方式
Iterator iterator(): 返回此集合中的元素迭代器,通过集合的iterator()方法得到
迭代器是通过集合的iterator()方法得到的,所以我们说它是依赖集合而存在的
Iterator中的常用方法
E next(): 返回迭代中的下一个元素
boolean hasNext():如果迭代具有更多元素,则返回true
public class CollectionDemo {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("hello");
c.add("world");
Iterator<String> it = c.iterator();
while(it.hasNext())
System.out.println(it.next());
}
}
Student自定义类的List
public class CollectionDemo {
public static void main(String[] args) {
Collection<Student> c = new ArrayList<Student>();
Student s1 = new Student("awin1",18);
Student s2 = new Student("awin2",19);
Student s3 = new Student("awin3",20);
c.add(s1);
c.add(s2);
c.add(s3);
Iterator<Student> it = c.iterator();
while(it.hasNext())
System.out.println(it.next().getName());
}
}
List集合概述
有序集合(也称为序列),用户可以精准控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素
与Set集合不同,列表通常允许重复的元素
与collection不同,list扩展了collection的方法 list.get(i) ListIterator列表迭代器
List集合的特点
有序:存储和取出的元素顺序一致
可重复:存储的元素可以重复
并发修改异常ConcurrentModificationException
产生原因:迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致。
解决方案:用for循环遍历,然后用集合对象做对应的操作即可
ListIterator列表迭代器
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("javaee");
ListIterator<String> lit = list.listIterator();
while(lit.hasNext()){
System.out.println(lit.next());
}
while(lit.hasPrevious()){
String s = lit.previous();
System.out.println(s);
}
增强for循环
增强for:简化数组和Collection集合的遍历,本质上是一个Iterator迭代器
实现Iterable接口的类允许其对象称为增强类
ArrayList的底层实现就是数组;
LinkedList的底层是链表;
Set集合特点
不包含重复元素的集合
没有带索引的方法,所以不能使用普通for循环遍历
HashSet:对集合的迭代顺序不做任何保证
哈希值:
是JDK根据对象的地址或者字符串或者数字计算出来的int类型的数值
LinkedHashSet集合特点
哈希表和链表实现的set接口,具有可预测的迭代次序
由链表保证元素有序,也就是说元素的存储和取出的顺序是一致的
由哈希表保证元素唯一,也就是说没有重复的元素
TreeSet集合
元素有序,按照一定的规则进行排序,具体排序方式取决于构造方法
TreeSet(): 根据其元素的自然排序进行排序
TreeSet(Comparator comparator):根据指定的比较器进行排序
没有带索引的方法,所以不能使用普通for循环遍历
由于是Set集合,所以不包含重复元素的集合
无参构造:自然排序Comparable(接口)
重写compareTo方法(主要排序和次要排序都要写)
带参数构造:比较器排序Comparator (接口)
让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法匿名内部类
泛型
优点:
把运行时期的问题提前到了编译期间
避免了强制类型转换
泛型类
泛型类的定义
public class Generic<T> {
private T t;
public T getT(){
return t;
}
public void setT(T t){
this.t = t;
}
}
泛型方法的定义
public class Generic {
public <T> void setT(T t){
return t;
}
}
泛型接口
public interface Generic<T>{
void show(T t);
}
类型通配符
类型通配符<?>
List<?>:表示元素类型未知的List,他的元素可以匹配任何的类型
这种带通配符的List仅表示他是各种泛型的父类,并不能把元素添加到其中。
类型通配符上限:<?extends类型>
类型通配符下限:<?super类型>
可变参数
这里的变量其实是一个数组
如果一个方法有多个参数,包含可变参数,可变参数要放在最后
代码
public static int sum(int... a) {
int sum = 0;
for (int i : a) {
sum += i;
}
return sum;
}
可变参数使用

Map
Map集合概述
Interface Map<K,V>
Interface Map<K,V>
将键映射到值的对象;不能包含重复的键;每个键可以映射到最多一个值
创建Map集合对象
多态
具体实现类
Collections
略
File
File:是文件和路径名的抽象表示
文件和目录是可以通过File封装成对象的
对于File而言,其封装的并不是一个真正存在的文件,而是一个路径名而已,它可以是存在的,也可以是不存在的,将来是要通过具体的操作把这个路径的内容转换为具体存在的。
字节流
IO流概述和分类
按照数据的流向
输入流:读数据
输出流:写数据
按照数据类型来分
字节流:字节输入流;字节输出流
字符流:字符输入流;字符输出流
一般来说,我们说IO流的分类是按照数据类型来分的
字节流抽象基类
InputStream:这个抽象类是便是字节输入流的所有类的超类
OutputStream:这个抽象类是表示字节输出流的所有类的超类
子类名特点:子类名称都是以其父类名作为子类名的后缀
FileOutputStream(String name)
字节缓冲流
BufferOutputstream()
进程
进程:是正在运行的程序
是系统进行资源分配和调用的独立单位
每一个进程都有它自己的内存空间和系统资源
线程:进程中的单个顺序控制流,则称为单线程程序
单线程:一个进程只有一条执行路径,则称为单线程程序
多线程:一个进程有多条执行路径,则称为多线程程序
多线程运行代码
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
// my1.run();
// my2.run();
my1.start();
my2.start();
}
}
线程调度
线程有两种调度模型
分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的实践片
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU的时间片相对多一点
JAVA使用的是抢占式调度模型
Thread类中设置和获取线程优先级的方法
setPriority(int newPriority)--------getPriority()
线程优先级:10,1,5
线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到想要的效果。
线程控制
static void sleep( long millis) 指定停留的毫秒数
void join( ) 等待这个线程死亡
void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
线程生命周期

新建-就绪-阻塞-运行-死亡
多线程的实现方式
继承Thread类
实现Runnable接口
相比继承Thread类,实现Runnable接口的好处
避免了Java单继承的局限性
适合多个相同程序的代码去处理同一个资源的情况,把线程和程序代码、数据有效分离,较好的体现了面向对象的设计思想
线程同步
线程安全问题:
是否多线程环境
是否有共享数据
是否有多条语句操作共享数据
同步代码块
synchronized (obj) {
if (tickets > 0) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + ": " + tickets);
tickets--;
}
}
同步方法:把 synchronized 关键字加到方法上
同步方法的锁对象:this
同步静态方法:static synchronized 返回值类型 方法名 (方法参数){ }
同步静态方法的锁对象是 SellTicket.Class
线程安全的类
StringBuffer
Vector
Hashtable
被synchronizedlist代替
Lock锁-接口
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock(): 获得锁
void unlock():释放锁
Lock是接口不能直接实例化,采用它的实现类ReentrantLock来实例化
生产者消费者模式概述
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify() 方法或 notifyAll() 方法
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程