一、数据类型
1、 四类八种:
取值范围小的可向大的转,大转小会失去精度
类型 | 名称 | 占用字节 | 取值范围 | 默认值 | 包装类 |
---|---|---|---|---|---|
整形 | byte 字节型 | 1(8 Bit) | -2^7 ~ 2^7-1 | 0 | Byte |
short 短整型 | 2(16 Bit) | -2^15 ~ 2^15-1 | 0 | Short | |
int 整形 | 4(32 Bit) | -2^31 ~ 2^31 - 1 | 0 | Integer | |
long 长整型 | 8(64 Bit) | -2^63 ~ 2^63-1 | 0L | Long | |
浮点型 | float 浮点型 | 4(32 Bit) | (7位有效数字) | 0.0f | Float |
double 双精度浮点型 | 8(64 Bit) | (16位有效数字) | 0.0d | Double | |
字符型 | char 字符型 | 2(16 Bit) | \u0000 ~ \uFFFF | \u0000 | Character |
布尔型 | boolean 布尔型 | 1/8(1 Bit) | false、true | false | Boolean |
2、String 类:字符串,由英文双引号括起来的由0到多个数字、字母、字符共同组成的一个串,可赋值为null。而char是由英文单引号括起来的1个数字或字符或字母。
二、访问权限修饰符、通配符
1、public:类内部、同个package、子类、任何地方。
2、protected:类内部、同个package、子类。即可以被子类和同一个包内的类访问。
3、default:类内部、同个package。即只可以被同一个包内的类访问。
4、peivate:类内部。即只可以被该类内部自己访问。
5、通配符:使用问号 " ? " 表示所有类型
class Test {
public void showInfo(List list) {
}
}
6、有限制的通配符:例如泛型为,表示只允许泛型类型为Person或Person子类的引用调用
三、关键字this、super、static、final、abstract
1、this:表示引用本类对象的属性、方法。先从自己的类中引用,若找不到,如果有父类,则会从父类里找。
2、super:子类用super来调用父类的属性、方法或构造器,可用于区分子父类之间同名的成员。super可以一直向上追溯多个层级的父类。
3、static:类变量(静态变量),不用通过实例化就能调用,直接 类名.该关键字变量名(Person.country),被所有这个类的实例化对象所共享。可修饰属性、方法、代码块和内部类。
public class Person {
static Stirng country;
int age;
Stirng name;
public static void getInfo() {
······
}
}
4、final:标记的变量为常量,只能被赋值一次,名称大写。标记的类不能被继承,标记的方法不能被重写
5、abstract:修饰类或方法,成为抽象类或抽象方法
四、集合、迭代、泛型
1、Map:具有映射关系的集合,保存具有映射关系的数据,即 key-value对 一对一
1-①:HashMap是Map的接口的典型实现
Map map = new HashMap();
1-②:TreeMap对所有的 key-value对 处于有序状态,默认为自然排序
2、List:有序,可重复的集合
2-①:ArrayList是List的接口的典型实现
List list= new ArrayList();
3、Set:无序、不可重复的集合
3-①:HashSet是Set接口的典型实现,存放值时根据值的hashcode值来决定存放位置。集合中的元素可以为null。
Set set = new HashSet();
3-②:TreeSet是SortSet接口的实现类,它会调用集合元素的compareTo(Object obj)方法,确保集合元素处于排序状态。必须放入同样类的对象。默认为自然排序,升序排列。
Set set = new TreeSet();
4、foreach 迭代集合:使用格式为
for(Object obj : 要迭代的对象) {
System.out.print(obj);
}
5、Iterator 迭代器遍历集合:使用格式为
Iterator it = set.iterator();
while(it.hashNext()) {
System.out.print(it.next());
}
6、foreach与Iterator的区别:Iterator 提供可在遍历时删除集合内元素的方法。
7、泛型<类/接口>:让集合只能存放相同类型的对象/类(若是数据类型用Integer、String之类的)。
Set set = new HashSett();
interface Generator {
T test(T t);
}
五、IO(字节流xxxStream与字符流xxxReader/xxxWriter)
flish():关于 flush() 的含义,清空缓冲区。
1、File类:能新建、删除和重命名文件和目录。(如需要访问文件内容则需要输入/输出流)
2、Input:计算机把外部数据(磁盘、光盘等储存设备)读到自己的代码程序中为“输入”。(计算机把外部数据输入到自己的程序中)
3、Output:计算机把代码程序中的数据写到磁盘、光盘等储存设备中为“输出”。(计算机把程序的数据输出到磁盘中)。
4、文件流(数据流的读写是基于文件的操作)计算机与硬盘之间的io操作,读写较慢
4-①:FileInputStream 字节输入流
public static void main(String[] args) {
try {
FileInputStream in = new FileInputStream("文件位置");
// 把文件字节输入流放到缓冲字节输入流对象里
// BufferedInputStream bis = new BufferedInputStream(in);
// 读取的是字节
byte[] b = new byte[100];
in.read(b);
// 从磁盘中读取xx个字节的数据进入到程序中
System.out.println(new String(b));
// 最晚开的最早关
// bis.cloce();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
4-②:FileOutputStream 字节输出流
public static void main(String[] args) {
try {
// 后面 true 为追加模式(append),避免直接覆盖
FileOutputStream out = new FileOutputStream("文件位置", true);
// 把文件字节输入流放到缓冲字节输入流对象里
// BufferedOutputStream bos = new BufferedOutputStream(out);
String str = "一段文本";
// 从程序中输出字节到磁盘中保存
out.write(str.getBytes());
out.flush();
// 最晚开的最早关
// bos.cloce();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
4-③:FileReader 字符输入流
public static void main(String[] args) {
try {
FileReader fr = new FileReader("文件位置");
// 把文件字节输入流放到缓冲字节输入流对象里
// BufferedReader br= new BufferedReader (fr);
// 读取的是字符
char[] c = new char[45];
fr.read(c);
System.out.println(new String(c));
// 最晚开的最早关
// br.cloce();
fr.close();
} catch (Exception e) {
e.printStackTrace();
}
}
4-④:FileWriter 字符输出流
public static void main(String[] args) {
try {
FileWriter fw = new FileWriter("文件位置", true);
// 把文件字节输入流放到缓冲字节输入流对象里
// BufferedWriter bw= new BufferedWriter(fw);
String str = "一段文本";
fw.write(str);
fw.flush();
// 最晚开的最早关
// bw.cloce();
fw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
5、缓冲流(数据流的读写是基于内存的操作)把数据先缓冲进内存里,能提高数据的读写速度
5-①BufferedInputStream
5-②BufferedOutputStream
5-③BufferedReader
5-④BufferedWriter
6、转换流(字节流、字符流之间转换)
6-①InputStreamReader:字节输入流转换成字符输入流
例:
FileInputStream fis = new FileInputStream("文件位置");
InputStreamReader isr = new InputStreamReader(fis, "编码格式");
6-②OutputStreamWriter:字节输出流转换成字符输出流
FileOutputStream fos = new FileOutputStream("文件位置");
OutputStreamWriter isr = new OutputStreamWriter(fos, "编码格式");
7、随机存取流
7-①:RandomAccessFile类:程序可随机跳到文件的任意地方来读写文件。
// "r":只读
RandomAccessFile ra = new RandomAccessFile("文件位置", "r");
// 文件起始点从零开始
ra.seek(0);
// 设置读取字符串的长度
bytep[] b = new byte[100];
System.out.print(new String(b));
8、对象流
8-① 序列化 Serialize :将对象转化成字节流储存在硬盘中。用 ObjectInputStream类将一个Java对象(该java对象必须实现Serializable接口)写入IO流中,凡是实现 Serializable 接口的类,都有一个序列化版本标识符的静态变量。若要序列化和反序列化同一个对象,则要保证serialVersionUID是相同的。
实现序列化接口的对象 Person.java:
import java.io.Serializable;
public class Person implements Serializable{
// serialVersionUID 用来表明类的不同版本,若不自定义则会自动随机生成
private static final long serialVersionUID = 1L;
int age;
String gender;
String name ;
public Person() {}
public Person(int age, String gender, String name) {
this.age = age;
this.gender = gender;
this.name = name;
}
public int getAge() {
return age;
}
public String getGender() {
return gender;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setName(String name) {
this.name = name;
}
}
序列化
public static void main(String[] args) {
Person p = new Person(25, "男", "张三");
try{
// 用对象流将java对象转换为字节流,并输出到指定文件位置
FileOutputStream fos = new FileOutputStream("文件位置");
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 对象流将对象写入文件中
oos.writeObject(p);
// System.out.println(p.getName());
oos.flush();
oos.close();
}catch(Exception e){
e.getStackTrace();
}
}
8-② 反序列化 Deserialize:将字节流转化成对象。用ObjectOutputStream类从IO流中恢复该Java对象。
反序列化
public static void main(String[] args) {
try{
// 计算机读取(输入)文件,用对象流将字节流转换为java对象,并保存到对应类型(强制转换)的类中
FileInputStream fis = new FileInputStream("文件位置");
ObjectInputStream ois = new ObjectInputStream(fis);
Person p = (Person)ois.readObject();
ois.close();
System.out.println("反序列化:" + p.getName());
}catch(Exception e){
e.getStackTrace();
}
}
六、类
1、方法重载(同一个类里)
方法名相同,但参数个数不同、参数的数据类型不同或者返回值的数据类型不同。
2、方法重写(子类对父类方法的覆盖)
子类对父类的方法重写,只能重写方法体的代码(不能使用更严格的访问权限、抛出的异常不能大于父类被重写方法的异常、同为static或!static)。
3、初始化块{······}(无static修饰)
执行顺序:静态代码块(只执行一次)-->初始化块(每次实例化一个对象都执行一次)-->构造方法,按顺序执行多个初始化块。
4、抽象类 abstract
特征通用的父类,不能被实例化,里面的方法必须被子类重写,方法体由子类提供,且重写方法时方法上面需添加 @override
5、接口 interface
一个类里可实现多个接口,接口可继承其他接口,实现接口类用 class xxx implements 接口类名{}。接口中所有的成员变量默认都是public static final修饰的,所有方法默认都是public abstract修饰的(这些修饰符可忽略不写),接口没有构造器。
抽象类与接口的区别:抽象是对一类事物的高度抽象,包括属性和方法,形成一个父类;接口是对一系列动作的抽象,实现相应的接口即为完成一系列动作。
6、枚举类 enum
public enum Season{
SPRING("春天","百花");
SUMMER("夏天","热烈");
AUTUMN("秋天","萧瑟");
WINTER("冬天","埋葬");
private final String name;
private final String desc;
prinvate Season(Strinf name, String desc) {
this.name = name;
this.desc = desc;
}
public void showInfo() {
System.out.print(this.name + ":" + this.desc);
}
}
public class Test {
public static void main(String[] args) {
Season spring = Season.SPRING;
speing.showInfo();
}
}
7、自定义工具类Utils.java
里面放入许多个需要反复执行的方法。
8、Collections操作集合的工具类
是一个操作List、Map、Set等集合的工具类。此类提供了大量方法对集合元素进行排序、查询和修改等操作。也可解决多线程并发访问集合时的线程安全等问题。
9、Javabean:是一个包装好的可以重复使用的组件,一种特殊的Java类——public 类、有一个无参的公共构造器、private 属性、且有对应的 public get set方法用来修改和获取属性、(也可以有一系列的操作方法和事件处理器)。
七、线程
生命周期:新建(执行start()方法之前,线程实例创建,尚未被启动)、就绪(执行start()方法之后,在就绪队列中排队)、运行(获得CPU资源正在执行run()方法)、阻塞(正在运行的线程让出CPU并暂停自己的执行run()方法,可通过wait()进入等待状态、或通过在获取同步锁失败后进入等待状态、或通过sleep()或join()使线程进入阻塞状态,阻塞状态终止之后会转回就绪状态重新排队)、死亡(自然终止或用stop()方法终止)
1、程序:某种语言编写的一组指令的集合。
2、进程:是程序的实体,一个程序至少有一个进程,存在它自身的产生、存在和消亡。
3、线程:是进程的一个实体,一个线程只能属于一个进程,而一个进程至少有一个线程,线程是程序内部的一条执行路径。(若程序可同一时间执行多个线程,则这个程序就是支持多线程的)
4、同步与异步:同步是指一个进程在执行某个请求的时,若该请求需要一段时间才能返回信息,那么这个进程会一直等待,直到接收了返回信息才能继续执行下去;异步是指进程不需要一直等待,在接受返回信息的期间,可以继续执行下面的操作,不管其他进程的状态。
5、创建线程的两种方式:
5-①继承Thread类:自定义子类继承→子类重写 run() →创建Thread子类对象→子类对象调用线程 strat()
执行多次后可发现线程是异步进行的(main线程与ThreadSon线程)
public class Test {
public static void main(String[] args) {
String name = Thread.currentThread().getName();
Thread thread = new ThreadSon();
thread.start();
System.out.println(name + " 线程:4");
System.out.println(name + " 线程:5");
System.out.println(name + " 线程:6");
}
}
class ThreadSon extends Thread{
@Override
public void run() {
System.out.println("1");
System.out.println("2");
System.out.println("3");
}
}
输出结果为:
>>
main 线程:4
>>1
>>main 线程:5
>>2
>>3
>>main 线程:6
5-②实现 Runnable 接口:
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new toSleepRunnble());
// 或者使用如下方式:
// Runnable run = new toSleepRunnble();
// Thread thread = new Thread(run);
thread.start();
System.out.println("4");
System.out.println("5");
System.out.println("6");
}
}
class toSleepRunnble implements Runnable{
@Override
public void run() {
System.out.println("1");
System.out.println("2");
}
}
5、相关方法
5-① yield() 线程让步:让其它优先级相同或更高且数据量更大的线程优先获得运行机会,此时调用此方法的线程回到就绪状态重新排队(效果有待商榷)
5-② join():即使是低优先级的线程也可以获得先执行权。即阻塞当前所在类的活动线程,让调用了 .join() 方法的线程插入进来活动,等到join进来的线程执行完毕,再让main方法执行其它被阻塞的线程。
关于join()的使用实例
public class Test {
public static void main(String[] args) {
String name = Thread.currentThread().getName();
Thread thread = new ThreadSon();
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " 线程:4");
System.out.println(name + " 线程:5");
System.out.println(name + " 线程:6");
}
}
class ThreadSon extends Thread{
@Override
public void run() {
System.out.println("1");
System.out.println("2");
System.out.println("3");
}
}
输出结果为:
>>
1
>>2
>>3
>>main 线程:4
>>main 线程:5
>>main 线程:6
若不添加 join() 则:
>>
1
>>main 线程:4
>>main 线程:5
>>2
>>main 线程:6
>>3
5-③ sleep(long millis):让当前活动线程“沉睡”指定的时间,而其他线程先执行,但不会释放占用的同步锁,时间到后释放锁,回到就绪状态重新排队。
5-④ wait():令当前线程挂起并放弃CPU、同步资源(同步锁),使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问。需要使用notify()来唤醒。
关于sleep() 与wait()的区别: wait()(或wait(long))是Object的一个方法,且需放在同步锁的方法中,且须使用notify()(或timing out)才能唤醒该线程占有的同步锁;sleep()是Thread的一个方法,若该线程在使用了sleep()的前面又使用了同步锁,则需要等线程主动醒来才能退出阻塞状态且释放这个锁。
5-⑤ notify():唤醒正在排队等候同步资源的线程中优先级最高者结束等待。
关于wait() 与 notify() 的简析
5-⑥ stop():强制线程生命期结束
5-⑦ boolean isAlive():判断线程是否还活着
关于 wait() 和 notify() 的使用实例——消费者与生产者问题
题目:生产者(Producer)将产品交给店员(Clerk,相当于仓库),消费者(Customer)从店员处取走产品。店员一次只能拿 20 个产品(生产者最大生产量),如果生产者试图生产更多产品,店员会叫生产者停一下,等待下次通知才能生产。当店员拿来产品,店员会通知消费者前来消费。如果店中的产品数量不足,店员会叫消费者等一下,等待下次店员拿来了产品后通知了消费者,消费者才能消费。
一个生产者线程与一个消费者线程
public class TestThread {
public static void main(String[] args) {
/**
* 多个线程排队获取CPU的使用权
* 情况一:当其中一个线程获取了同步锁,并使用了 wait() 方法后,这个线程从运行状态退出,
* 进入等待阻塞状态(进入等待队列),同时释放所持有的同步锁(先获取锁再释放锁)
* 直到被另一个线程使用 notify() 唤醒,其它的线程才从阻塞状态进入到可运行状态。(前提是这几个个线程等待的是同一共享资源(同一个同步锁))
* 情况二:当其中一个线程获取了锁,根据判断条件若不使用 wait() 方法,那么将直接执行 wait() 下面的代码
* 等这些步骤执行完,就可以使用 notify()或notifyAll() 方法唤醒(通知)其它线程去排队获取同步锁
*/
Clerk clerk = new Clerk(); //店员
Thread producThread = new Thread(new Producer(clerk), "生产者");
Thread customerThread = new Thread(new Customer(clerk), "消费者");
producThread.start();
customerThread.start();
}
}
//店员
class Clerk{
// public int totalProduct = 0; //店员身上有 0 个产品
// public boolean isEnough = false; //产品不足
public int totalProduct = 20; //店员身上有 20 个产品,初始化时预设店员身上产品充足
public boolean isEnough = true; //产品是否足够,false为库存不足,true为库存足够
public static final int MAX_PRODUCT = 20; //店员身上最大的产品数为20
}
//生产者
class Producer implements Runnable{
Clerk clerk;
String name;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
/**
* 注意使用 while 在外围包含着同步锁的原因:为了让生产者无线次数地生产产品,消费者不断地消费产品。
* 若去掉外面的 while 循环的话,生产者线程只能被 notify 一次,即只能执行一次 wait() 后面的操作,造成生产者线程只能执行一次 run()
* 当生产者线程再次获取cpu使用权时,因为run方法已经执行完毕,就会自动 exit() 结束线程。徒留消费者线程一直在 wait 中。
*/
while(true){
synchronized(clerk){
try {
//获取当前线程的名称
name = Thread.currentThread().getName();
/**
* 当判断产品足够时,执行wait()方法,同步锁被释放,生产者线程进入等待阻塞队列,不执行后面的操作
* 关于wait()方法放在while循环里是为了防止线程被错误地、提早地被唤醒
*/
while(clerk.isEnough){
System.out.println( name + " 拿到了同步锁,发现货物还足够,于是继续等待···");
clerk.wait();
System.out.println( name + " 再次拿到了同步锁,从 wait() 中被唤醒!");
}
//需要操作的代码块放在 wait()下面是因为,当线程被 notify 之后是从 wait() 下面的代码块继续执行的
System.out.println(name + " 正在生产产品···");
while(clerk.totalProduct < clerk.MAX_PRODUCT){
//生产者每隔 3 秒生产 5 个产品
Thread.sleep(3000);
if(clerk.MAX_PRODUCT - clerk.totalProduct >= 5){
clerk.totalProduct += 5;
System.out.println("---->已经生产 5 个产品,现在产品库存为: " + clerk.totalProduct + " 个产品!");
}
else{
int num = clerk.MAX_PRODUCT-clerk.totalProduct;
clerk.totalProduct += num;
System.out.println("---->已经生产 " + num + " 个产品,现在产品库存为: " + clerk.totalProduct + " 个产品!");
Thread.sleep(1000);
}
}
System.out.println("生产完毕,产品数量已充足,请消费者继续购物~~~");
clerk.isEnough =true;
Thread.sleep(3000);
//因为此时消费者线程在等待阻塞状态,所以使用 notify() 时,在等待状态的线程收到通知后退出等待队列
//多个消费者线程时使用 clerk.notifyAll();
clerk.notify();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
//消费者
class Customer implements Runnable{
Clerk clerk;
String name;
int num;
public Customer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
//不断有消费者来购买产品
while(true){
synchronized(clerk){
try {
name = Thread.currentThread().getName();
//当产品不足时等待,且释放同步锁,让其它线程有机会获取同步锁
while(clerk.isEnough == false){
System.out.println( name + " 拿到了同步锁,发现货架上产品不够了,于是继续等待···");
clerk.wait();
System.out.println( name + " 再次拿到了同步锁,从 wait() 中被唤醒!");
}
//消费者每次执行一次购买产品的操作
System.out.println( name + " 进来了,正在消费···");
//消费者随机购买[1,8]个产品数(即最多买 8 个产品,最少买 1 个)
num = (int)(Math.random() * 8) + 1;
if(clerk.totalProduct >= num){
//消费者每隔 2 秒消费 num 个产品
Thread.sleep(2000);
clerk.totalProduct -= num;
System.out.println("----> " + name + " 购买了 " + num + " 个产品,现货柜剩余: " + clerk.totalProduct + " 个产品!");
Thread.sleep(1000);
System.out.println(name + " 离开了!");
Thread.sleep(1000);
System.out.println("请下一位消费者上前购物~~~");
}else{
Thread.sleep(2000);
System.out.println(name + " 需要购买 " + num + " 个产品,看到货柜产品数量不太够自己的需求而离开了!店员正在提醒生产者生产产品~~~");
clerk.isEnough = false;
}
Thread.sleep(3000);
//因为此时生产者线程在排队等待的状态,所以使用 notify() 时,在等待状态的线程 收到通知后退出等待队列
//如果有多个线程则 clerk.notifyAll();
clerk.notify();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
结果如下:
一个生产者线程与多个消费者线程
public class TestThread {
public static void main(String[] args) {
Clerk clerk = new Clerk(); //店员
Thread producThread = new Thread(new Producer(clerk), "生产者");
Thread customerThread = new Thread(new Customer(clerk), "阿大");
Thread customerThread2 = new Thread(new Customer(clerk), "小二");
Thread customerThread3 = new Thread(new Customer(clerk), "张三");
Thread customerThread4 = new Thread(new Customer(clerk), "李四");
Thread customerThread5 = new Thread(new Customer(clerk), "王五");
producThread.start();
customerThread.start();
customerThread2.start();
customerThread3.start();
customerThread4.start();
customerThread5.start();
}
}
同时把 notify() 替换为 notifyAll();
结果如下:
需要注意的问题如下:
一、wait() 方法在 while 里面而不在 if 里面的原因:可能会出现某一线程被 提前唤醒 通知去获取同步锁(多线程的情况下)
若 wait() 在 if 语句里if(condition){ obj.wait(); }
那么线程执行 run 方法时可能会出现以下这种情况:A线程按顺序执行 if 语句→ 若
condition == true
→ A线程执行 wait()方法 放弃同步锁,进入等待阻塞队列,此时A线程的 run() 方法尚未执行完 → 另一个在排队的B线程抢到同步锁,获取同步锁的B线程执行完一系列操作,然后执行 notifyAll()方法 唤醒其它线程,B线程的 run()方法 执行完毕 → C线程被唤醒,并且抢到同步锁的C线程执行完一系列操作,然后执行 notifyAll()方法 唤醒其它线程 → ······ → 直到A线程从 wait() 中被其它线程的 notify() 唤醒,抢到同步锁的 A线程 不再从最开始的 if语句 开始判断,而是直接执行 wait() 后面的语句——执行完 if 语句 范围内的 wait() 后面的语句,再执行 if 语句 范围外的 wait() 下方的语句(在这一步中,如果某些条件已经被改变了而A线程又不做判断的话,很容易导致出错!),执行完一系列操作后接着执行 notifyAll() 方法 唤醒其它线程,此时A线程的 run() 方法执行完毕 → (其它线程被唤醒并执行一系列操作······) → A线程在排队时再次抢到同步锁,然后按顺序执行 if 语句 → ······若 wait() 在 while 语句里
while(condition){ obj.wait(); }
那么线程执行 run 方法时会出现以下这种情况:A线程按顺序执行 while 语句→ 若
condition == true
→ A线程执行 wait()方法 放弃同步锁,进入等待阻塞队列,此时A线程的 run() 方法尚未执行完 → 另一个在排队的B线程抢到同步锁,获取同步锁的B线程执行完一系列操作,然后执行 notifyAll()方法 唤醒其它线程,B线程的 run()方法 执行完毕 → C线程被唤醒,并且抢到同步锁的C线程执行完一系列操作,然后执行 notifyAll()方法 唤醒其它线程 → ······ → 直到A线程从 wait() 中被其它线程的 notify() 唤醒,抢到同步锁的 A线程 不再从最开始的 while 语句 开始判断,而是直接执行 wait() 后面的语句——执行完 while 语句 范围内的 wait() 后面的语句,再因为 while 循环 而回到之前再次判断condition
是否为true
,若condition == true
则A线程放弃同步锁,继续等待;若condition == false
,则A线程跳出 while 循环,执行 while 语句 范围之外的 wait() 下方的语句,执行完一系列操作后接着执行 notifyAll() 方法 唤醒其它线程,此时A线程的 run() 方法执行完毕 → (其它线程被唤醒并执行一系列操作······) → A线程在排队时再次抢到同步锁,然后按顺序执行 where 语句 → ······二、synchronized() 锁外面被 while(true) 包含:为了将生产者这个线程不断生产和消费者这个线程不断消费,所以要在锁外面用 while(true) 设置死循环(根据情况,也可以将 true 改为其他限制条件)
6、同步锁 synchronized:一个线程访问一个对象中的synchronized同步代码块时,其他试图访问该对象的线程将被阻塞。
模拟用户的取票操作(用户先来的先取票,后来的后取票)
6-①:在方法声明中加上锁,表示这个方法为同步方法
public class Test {
public static void main(String[] args) {
TicketMachine tm = new TicketMachine();
// 新建线程且自定义线程名称
Runnable user1_run = new UserRunnable(tm, 47);
Thread user1 = new Thread(user1_run, "第一个用户");
Runnable user2_run = new UserRunnable(tm, 5);
Thread user2 = new Thread(user2_run, "第二个用户");
user1.start();
user2.start();
}
}
// 取票机器
class TicketMachine{
int total = 50;
String threadName;
public synchronized void getTicket(int n){
threadName = Thread.currentThread().getName();
if(total >= n){
System.out.println(threadName +" 操作中,原有" + total + "张票!");
total -= n;
System.out.println(threadName +" 现在已经取了" + n + "张票了!,还剩" + total + "张票!!");
}else{
System.out.println("票已经不足,请前往另一台机器取。");
}
}
}
class UserRunnable implements Runnable{
TicketMachine tm;
int n;
// 带参数的构造方法
public UserRunnable(TicketMachine tm, int n) {
this.tm = tm;
this.n = n;
}
@Override
public void run() {
// 用户从机器中取了n张票
tm.getTicket(n);
}
}
不加同步锁
加了同步锁
或
6-②:在方法中加上对象锁(锁住同一个“取票机器”这一对象)
public void getTicket2(int n, TicketMachine tm){
synchronized(tm){
name = Thread.currentThread().getName();
if(total >= n){
System.out.println(name+"操作中,原有" + total + "张票!");
total -= n;
System.out.println(name+"现在已经取了" + n + "张票了!,还剩" + total + "张票!!");
}else{
System.out.println("票已经不足,请"+name+"前往另一台机器取。");
}
}
}
// 相应main方法里的传参变化
public static void main(String[] args) {
TicketMachine tm = new TicketMachine();
// 新建线程且自定义线程名称
Runnable user1_run = new UserRunnable(47, tm);
Thread user1 = new Thread(user1_run, "第一个用户");
Runnable user2_run = new UserRunnable(5, tm);
Thread user2 = new Thread(user2_run, "第二个用户");
user1.start();
user2.start();
}
八、模式
1、单例设计模式
若实例化一个对象要占用大量的时间和资源,则只实例化一个对象。
2、模板方法设计模式
抽象类(父类)作为多个子类的通用模板,子类在抽象类的基础上扩展、改造。
3、工厂方法
通过工厂把new对象隔离,通过接口来接受不同产品的实现类。
例:面包的生产
Bread接口以及Bread类
// 面包接口
public interface Bread {
// 面包的原材料
void showMaterialsInfo();
// 面包的口味介绍
void showTasteInfo();
}
// 奶油面包
class CreamBread implements Bread{
@Override
public void showMaterialsInfo() {
System.out.println("奶油面包的原材料:牛奶、黄油、干酵粉、砂糖,盐,和鸡蛋。");
}
@Override
public void showTasteInfo() {
System.out.println("奶油香甜顺滑,面包松软。");
}
}
// 芝士面包
class CheeseBread implements Bread{
@Override
public void showMaterialsInfo() {
System.out.println("芝士面包的原材料:高粉、低粉、鸡蛋液、水、黄油、白糖、盐、酵母。");
}
@Override
public void showTasteInfo() {
System.out.println("刚出炉的芝士面包可拉丝,口感丰富、营养美味。");
}
}
// 法棍
class Baguette implements Bread{
@Override
public void showMaterialsInfo() {
System.out.println("法棍的原材料:面粉,酵母,盐,水。");
}
@Override
public void showTasteInfo() {
System.out.println("表皮松脆,内心柔软而稍具韧性,越嚼越香,充满麦香味。");
}
}
BreadFactory接口以及BreadFactory的类
// 面包工厂的接口
public interface BreadFactory {
// 生产之后的返回类型为面包类
Bread productBread();
}
// 奶油面包工厂接上面包工厂接口类生产奶油面包
class CreamBreadFactory implements BreadFactory{
@Override
public Bread productBread() {
// 奶油面包生产过程
System.out.println("生产奶油面包中···");
// 生产完毕返回一个奶油面包类
return new CreamBread();
}
}
class CheeseBreadFactory implements BreadFactory{
@Override
public Bread productBread() {
// 芝士面包生产过程
System.out.println("生产芝士面包中···");
// 生产完毕返回一个奶油面包类
return new CheeseBread();
}
}
class BaguetteFactory implements BreadFactory{
@Override
public Bread productBread() {
// 法棍生产过程
System.out.println("生产法棍中···");
// 生产完毕返回一个法棍类
return new Baguette();
}
}
main
public class Test{
public static void main(String[] args) {
// 面包通过奶油面包加工厂来生产奶油面包
Bread creamBread = new CreamBreadFactory().productBread();
creamBread.showMaterialsInfo();
creamBread.showTasteInfo();
// 面包通过芝士面包加工厂来生产芝士面包
Bread cheeseBread = new CheeseBreadFactory().productBread();
cheeseBread.showMaterialsInfo();
cheeseBread.showTasteInfo();
}
}