序列化:
序列化:把对象按照流一样的方式存到文本文件或者数据库或者网络中传输等等。
对象 -- 流数据:ObjectOutputStream
反序列化:把文本文件中的对象或者网络中的流数据给还原成一个对象的过程。
流数据 -- 对象:ObjectInputStream
//写数据,序列化,将一个对象存到一个文件中,这个过程叫做序列持久化。
未序列化异常:序列化完成后对内容进行修改,再进行反序列化报错。
NotSerializableException
只有支持java.io.Serializable接口的对象才能写入流中。
类的序列化由实现java.io.Serializable接口的类启用。
不实现此接口的类将不会使任何状态序列化或反序列化。
可序列化类的所有子类型都是可序列化的。
通过观察API发现,Serializable接口中没有任何抽象方法和常量,说明它是一个标记接口。
原因分析:
根据刚刚简单的分析后发现,由于Person类实现了Serializable标记接口,它本身应该有一个标记值,通过该类创建的对象也应该有着一样的标记值,假设一开始类的标记值是id=100
在没有进行修改类之前:
Person.class -- id=100
写数据的时候:object -- id=100
读数据的时候:object -- id=100
在进行修改后(删除了一个private):
Person.class -- id=200
之前写的时候:object -- id=100
读数据的时候:object -- id=100
在实际开发中,因为业务的问题,不允许重复地往文件中或者数据库中重复写入,那怎么解决呢?
--这个问题本质上是由于id值地不匹配导致地,如果说有一个办法,无论我怎么修改class类,这个id值都不会变化就好了。Java在序列化中提供了一个ID值,可以让我们去设定。我们不需要手动去设定,自动生成即可。
--idea--File--setting--Editor--Inspections--搜索-serialVersionUID--选择最下面的一个--Apply--点击对象名,Alt+回车
java提供了一个关键字给我们使用,可以让我们在序列化的时候选择哪些成员变量不被序列化--transient
--transient int age;
多线程:
要想学习多线程,就得先知道什么是线程,要想知道线程,就得先知道什么是进程。
进程:
是指正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和资源。
通过任务管理器看
线程:
是进程的单个顺序控制流,或者就是说是一个单独执行的路径
如果一个进程只有一条执行路径,称之为单线程
如果一个进程有多条执行路径,称之为多线程
线程是包含在进程中的。
举例:扫雷,360杀毒软件,百度网盘
学习多线程之前需要了解的几个关键词:
1、串行,是指一个程序中所有的任务都是按照先后顺序执行的,在前一个任务还没有处理完的情况下,是不会进行处理下一个任务的。
举例:理发店只有一个理发师,很多人去理发,就需要排队,就有先后顺序,先等前面的人理完发,再轮到后面的人。
2、并行,是指将任务分给不同的处理器去处理,每一个处理器中的任务再进行串行处理。
举例:火车站上有很多卖票窗口,多个窗口同时卖票,但是针对于某一个窗口来说,是一个接着一个去处理的。
3、并发,是指一个现象,并发需要处理器的支持。比如在处理一个任务的时候,操作系统可以调用资源去处理其他的任务,这个任务并行还是串行都可以。
无论是串行还是并行,都需要处理器支持并发。
举例:假设喝水是一个任务,每个火车站售票员,他再售票的同时也能喝水,这就表示支持并发。
JVM启动的时候是单线程还是多线程呢?
--多线程
JVM启动的时候,相当于启动了一个程序,就是启动了一个进程。其中包含了主线程,以及垃圾回收线程。
在启动JVM的时候,最低的要求是需要启动两个线程,所以JVM启动的时候是多线程程序。
创建线程的第一种方式:继承Thread类,重写run方法
1、创建一个自定义类继承Thread类
2、这个继承的类要重写run方法
3、根据这个类创建线程对象
4、启动线程
//单纯地调用run方法仅仅表示的是第一个对象调用普通的方法,所以这里的执行还是按照自上而下顺序执行,所以这里依旧是单线程程序
//要想看到多线程程序的执行效果,就必须换一种方式启动线程。start()
(面试题:线程调用start()和run()方法的区别?)
run方法中仅仅是封装了被线程执行的逻辑代码,因此直接对象调用run方法与普通的方法调用没有任何区别。
start()方法的调用,首先做的是启动一个线程,然后再由JVM去调用该线程对象中的run()方法。
(对于一个线程来说,不能启动多次)
注意:
1、启动线程调用的是start()方法
2、线程的调用start()方法先后顺序对今后真正执行的顺序没有影响
给一个线程设置名字:
1、public final void setName(String name)
2、通过构造方法给线程起名字Thread(String name)
获取一个线程的名字:
public final String getName()
多线程的实现方式2:实现Runnable接口,实现run方法
1、自定义一个类实现Runnable接口
2、实现run方法
3、创建实现Runnable接口类对应的对象
4、借助Thread类创建线程对象,将自定义类作为构造方法的参数传入
//创建实现Runnable接口类对应的对象
//创建多个线程对象(Thread t1 = new Thread(myRunnable1,"小白");)
//启动线程
//由于Runnable接口没有getName()方法,所以在实现接口的地方无法直接调用获取线程的名字。
//只能间接调用:Thread类中有一个静态的方法
//public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
(System.out.println(Thread.currentThread().getName() + ":" + i);)
线程调度:
假如我们的计算机只有一个 CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
线程有两种调度模型:
1、分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用 CPU 的时间片
2、抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。***
我们在前几个代码中都没有设置优先级,所以我们猜测一定会有一个默认的优先级。默认的优先级是多少呢?--5
获取线程中优先级的方法:
public final int getPriority() 返回此线程的优先级。
设置线程优先级的方法:
public final void setPriority(int newPriority) 更改此线程的优先级。
public final static int MIN_PRIORITY = 1; 线程可以拥有的最小的优先级
public final static int MAX_PRIORITY = 10; 线程可以拥有的最大的优先级
注意事项:
1、线程的默认优先级是5
2、设置优先级的时候,范围是1-10
3、线程的优先级越高仅仅表示的是获取CPU时间片的机率会高一些,并不能保证一定会先执行
休眠线程(睡眠线程):
休眠线程(睡眠线程)
public static void sleep(long millis)
线程加入:
线程加入
public final void join()
线程对象调用该方法的时候,目的是让调用该方法的当前线程先执行完,执行完毕后,再让其他线程执行。其他没有调用join方法的线程,他们之间还是会抢CPU执行权的。
注意:join方法的调用,必须是紧跟着当前线程start()方法后调用,否则不起作用。
//创建多线程环境
MyJoinThread t1 = new MyJoinThread();
MyJoinThread t2 = new MyJoinThread();
MyJoinThread t3 = new MyJoinThread();
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
礼让线程:
礼让线程
礼让线程的目的是暂停当前正在执行的线程,并让其他线程执行,它的作用实际上是为了让线程之间看起来更加和谐,它并不能保证多个线程之间一人一次。
后台线程(守护线程):
后台线程:(守护线程)
public final void setDaemon(boolean on)
Java中有两类:用户线程,守护线程
用户线程:我们在学习线程之前,运行起来的一个一个程序中的线程都是用户线程
守护线程:所谓的守护线程,指的是程序运行的时候,在后台提供了一个通用的服务线程,比如说垃圾回收线程,他就是一个守护线程。
(这种线程不一定是要存在的,但是可能程序会出问题。只要程序存在用户线程,程序就不会停止)
守护线程的设置
public final void setDaemon(boolean on)
注意事项:守护线程必须在启动之前进行设置
中断线程:
中断线程
public final void stop()
public void interrupt()
stop(); //强制打断睡眠,程序停止,这个方法不太好,这个方法已经被弃用
interrupt(); //打断睡眠,提示打断睡眠的错误,run方法中后面的代码继续执行,直到执行完毕。
线程安全:
为了模拟更加现实的售票场景,我们加入延迟操作;
加入延迟之后,产生了两个问题:
1、相同的票我们卖了很多次
CPU的执行操作是原子性,小小的CPU时间片足矣运行很多次
2、出了卖第0张票和负数的票。
这个是由于线程的执行具有随机性和延迟性导致的,因为我们加入了sleep休眠方法,让线程变成阻塞状态,让其他线程执行
上述案例中加入了延迟操作,出现了问题,其实这个问题现象就叫做:线程安全问题
要想解决这个问题,就得先搞清楚哪些原因导致的这个问题出现:
(这三个条件同时满足的情况下,就会出现线程安全问题)
1、是否存在多线程环境
2、是否存在共享变量(共享数据)
3、是否有多条语句操作着共享变量(共享数据)