1. 序列化流
1.1 序列化流概述
Java中提供了一种对象序列化的机制, 用一个字节序列可以表示一个对象,
该字节序列包含该[对象的数据]、[对象的类型]和[对象中储存的属性]等信息.
字节序列写出到文件之后, 相当于文件中[持久保存]了一个对象的信息.
反之, 字节序列还可以从文件中读取回来, 重构对象, 对他进行[反序列化].
1.2 序列化文件
代码:
FileOutputStream fileOut = new FileOutputStream ( "employee.txt" ) ;
ObjectOutputStream out = new ObjectOutputStream ( fileOut) ;
想要将一个对象序列化, 必须满足两个条件:
1. 该类必须实现Serializable接口, Serialazable接口是一个标记接口, 不包含任何方法, 不实现此接口的类会抛出NotSerializableException
2. 该类的所有属性必须是可序列化的, 如果有属性是不需要可序列化的, 需要注明瞬态(使用transient关键字修饰), 并且给该类生成一个序列化ID
public class Employee implements java. io. Serializable {
private static final long serialVersionUID = 1 L;
public String name;
public String address;
public transient int age;
public void addressCheck ( ) {
System. out. println ( "Address check : " + name + " -- " + address) ;
}
}
public final void writeObject ( Object obj) ;
public class SerializeDemo {
public static void main ( String [ ] args) {
Employee e = new Employee ( ) ;
e. setName ( "zhangsan" ) ;
e. setAddress ( "xuefulu" ) ;
e. setAge ( 20 ) ;
try {
ObjectOutputStream out = new ObjectOutputStream ( new FileOutputStream ( "employee.txt" ) ) ;
out. writeObject ( e) ;
out. close ( ) ;
System. out. println ( "Serialized data is saved" ) ;
} catch ( IOException i) {
i. printStackTrace ( ) ;
}
}
}
输出结果:
Serialized data is saved
1.3 反序列化
代码:
FileInputStream fileIn = new FileInputStream ( "employee.txt" ) ;
ObjectInputStream out = new ObjecInputStream ( fileIn) ;
public class DeserializeDemo {
public static void main ( String [ ] args) {
Employee e = null;
try {
FileInputStream fileIn = new FileInputStream ( "employee.txt" ) ;
ObjectInputStream in = new ObjectInputStream ( fileIn) ;
e = ( Employee) in. readObject ( ) ;
in. close ( ) ;
fileIn. close ( ) ;
} catch ( IOException i) {
i. printStackTrace ( ) ;
return ;
} catch ( ClassNotFoundException c) {
System. out. println ( "Employee class not found" ) ;
c. printStackTrace ( ) ;
return ;
}
System. out. println ( "Name: " + e. name) ;
System. out. println ( "Address: " + e. address) ;
System. out. println ( "age: " + e. age) ;
}
}
当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个`InvalidClassException`异常
发生这个异常的原因如下:
1.该类的序列版本号与从流中读取的类描述符的版本号不匹配
2.该类包含未知数据类型
3.该类没有可访问的无参数构造方法
2. 线程基础概述
2.1 什么是线程?
什么是程序?
程序是的概念,程序是为了解决某种问题,使用一种编程语言书写的一堆代码和指令的集合,例如网上下载的王者荣耀就是一个程序。
什么是进程?
进程是动态的概念,就是指运行在内容中的程序。一个程序运行起来,那么就形成进程!
什么是线程?
进程可以再一步进行细化,进程中每一个小的执行单元,称为一个线程
进程中包含线程,那么如果一个进程中包含多个线程,就是多线程程序。
2.2 多线程使用场合
在Java程序中要同时执行多个任务,例如多线程下载
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
后台运行的一些任务也会用到多线程.
........
2.3 如何创建多线程
2.3.1 继承Thread,重写父类的run()方法
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例
每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码
Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:
1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
2. 创建Thread子类的实例,即创建了线程对象
3. 调用线程对象的start()方法来启动该线程
public class MyThread extends Thread {
public MyThread ( String name) {
super ( name) ;
}
public void run ( ) {
for ( int i = 0 ; i < 20 ; i++ ) {
System. out. println ( getName ( ) + i) ;
}
}
}
public class TestMyThread {
public static void main ( String[ ] args) {
System. out. println ( "这里是main线程" ) ;
MyThread mt = new MyThread ( "小强" ) ;
mt. start ( ) ;
for ( int i = 0 ; i < 20 ; i++ ) {
System. out. println ( "旺财:" + i) ;
}
}
}
2.3.2 实现Runnable接口,重写run方法
采用Runnable也是非常常见的一种,我们只需要重写run方法即可
和继承自Thread类差不多,不过实现Runnable后,还是要通过一个Thread来启动:
1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正
的线程对象。
3. 调用线程对象的start()方法来启动线程。
public class MyRunnable implements Runnable {
@Override
public void run ( ) {
for ( int i = 0 ; i < 20 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + " " + i) ;
}
}
}
public class TestMyRunnable {
public static void main ( String[ ] args) {
MyRunnable mr = new MyRunnable ( ) ;
Thread t = new Thread ( mr, "小强" ) ;
t. start ( ) ;
for ( int i = 0 ; i < 20 ; i++ ) {
System. out. println ( "旺财 " + i) ;
}
}
}
通过实现Runnable接口,使得该类有了多线程类的特征
run()方法是多线程程序的一个执行目标, 所有的多线程代码都在run方法里面
Thread类实际上也是实现了Runnable接口的类
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
2.4 使用匿名内部类创建多线程
使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。
public class Demo1 {
public static void main ( String[ ] args) {
System. out. println ( "我是主线程........" ) ;
Runnable r= new Runnable ( ) {
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + "---->" + i) ;
}
}
} ;
new Thread ( r, "我是子线程A" ) . start ( ) ;
Thread t= new Thread ( ) {
@Override
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + "---->" + i) ;
}
}
} ;
t. setName ( "我是线程B" ) ;
t. start ( ) ;
for ( int i = 0 ; i < 100 ; i++ ) {
System. out. println ( Thread. currentThread ( ) . getName ( ) + "---->" + i) ;
}
}
}
2.5 Thread和Runnable的区别
相同点:
都是重写run方法,调用start方法启动
不同点:
继承Thread类方式,那么这个类就不能再继承别的类,有局限性
实现runnable接口,还可以继承别的类,避免了单继承的局限性,所以推荐使用实现接口的方式
实现Runnable接口,还可以方便的实现线程间资源的共享!
2.6 线程的生命周期
1. 新建(new):
新创建了一个线程对象。
2. 就绪态(runnable):
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法
该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
3. 运行(running):
可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
4. 阻塞(block):
阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行
直到线程进入可运行(runnable)状态,才有机会再次获得c pu timeslice 转到运行(running)状态
5. 死亡(dead):
线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期
死亡的线程不可再次复生
2.7 线程的调度
2.7.1 join方法
Thread提供了让一个线程等待另一个线程完成的方法join()方法
当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止
join()方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程
当所有的小问题都得到处理后,再调用主线程来进一步操作。例如当在主线程当中执行到t1.join()方法时,就认为主线程应该把执行权让给t1
public class JoinThread extends Thread {
public JoinThread ( String name) {
super ( name) ;
}
public void run ( ) {
for ( int i = 0 ; i < 100 ; i++ ) {
System. out. println ( getName ( ) + "" + i) ;
}
}
public static void main ( String[ ] args) throws Exception {
new JoinThread ( "新线程------>" ) . start ( ) ;
for ( int i = 0 ; i < 100 ; i++ ) {
if ( i == 20 ) {
JoinThread jt = new JoinThread ( "被Join的线程" ) ;
jt. start ( ) ;
jt. join ( ) ;
}
System. out. println ( Thread. currentThread ( ) . getName ( ) + "======>" + i) ;
}
}
}
上面程序中一共有3个线程,主方法开始时就启动了名为"新线程"的子线程, 该子线程将会和main线程并发执行
当主线程的循环变量i等于20时启动了名为"被Join的线程"的线程,该线程不会和main线程并发执行, main线程必须等该线程执行结束后才可以向下执行
在名为"被Join的线程"的线程执行时,实际上只有2个子线程并发执行,而主线程处于等待状态。运行上面程序
从上面的运行结果可以看出,主线程执行到i=20时启动,并join了名为"被Join的线程"的线程
所以主线程将一直处于阻塞状态,直到名为"被Join的线程"的线程执行完成。
2.7.2 线程优先级
不同的线程有不同的优先级, 范围区间为(1 ~ 10)之间, 线程默认的优先级为5
但是不能单纯的依靠调整优先级来决定执行的先后顺序,只不过优先级高抢占CPU的概率要大写。
不能依靠线程的优先级来决定线程的执行顺序!
2.7.3 sleep方法
如果需要让当前正在执行的线程暂停一段时,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现
当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会
即使系统中没有其他可执行的线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。
下面程序调用sleep()方法来暂停主线程的执行,因为该程序只有一个主线程
当主线程进入睡眠后,系统没有可执行的线程,所以可以看到程序在sleep()方法处暂停.
public class SleepTest {
public static void main ( String[ ] args) throws Exception {
for ( int i = 0 ; i < 10 ; i++ ) {
System. out. println ( "当前时间: " + new Date ( ) ) ;
Thread. sleep ( 1000 ) ;
}
}
}
上面程序中sleep()方法将当前执行的线程暂停1秒
运行上面程序,看到程序依次输出10条字符串,输出2条字符串之间的时间间隔为1秒.
2.7.4 yield方法
yield()方法是一个和sleep()方法有点相似的方法
它也是Threard类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程, 它只是将该线程转入就绪状态
yield()只是让当前线程暂停一下,让系统的线程调度器重新调度一次
完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。
2.7.5 yield方法和sleep方法的对比
1. sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态
而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态
因此完全有可能某个线程调用yield()方法暂停之后,立即再次获得处理器资源被执行
2. sleep()方法声明抛出了InterruptcdException异常
所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常
而yield()方法则没有声明抛出任何异常
3. sleep()方法比yield()方法有更好的可移植性
通常不建议使用yield()方法来控制并发线程的执行