序列化和反序列化
序列化 Serialize
将java对象存储到硬盘上(也可以是用于网络传输的字节流)中;
反序列化 Deserialize
将硬盘上的数据重新恢复到内存中,变成Java对象。
参考代码
public class SerializeTest {
public static void main(String[] args) {
Student student = new Student("关灯吃面","秋名RG");
try (
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student"))
) {
//序列化一个对象
//如果序列多个对象,可以用List集合
oos.writeObject(student);
oos.flush();
Object o = ois.readObject();
System.out.println(o);
} catch (FileNotFoundException fileNotFoundException) {
fileNotFoundException.printStackTrace();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Student implements Serializable {
private String hobby;
//在整个对象被序列化时,transient修饰的字段不会被序列化。
private transient String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
public Student(String hobby, String name) {
this.hobby = hobby;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"hobby='" + hobby + '\'' +
", name='" + name + '\'' +
'}';
}
}
需要注意的点
被序列化和被反序列化的对象,需要继承Serializable接口,这个接口是标志接口,当Java虚拟机看到这个接口时,就会知道这家伙需要序列化,于是就生成一个序列化版本号。
序列化版本号的作用
在反序列化时,首先通过 类名+序列化版本号 辨别应该反序列化成哪个类,如果 类名+序列化版本号 验证通过,则正常反序列化。
继承Serializable接口是否就足够了?
如果只是继承Serializable接口,JVM会自动生成一个序列化版本号,在序列化时,则将对象信息连带版本号一起序列化,进入硬盘或者字节流中(用于网络传输)。缺点是,如果在序列化之后,我们修改了类的代码,重启JVM,JVM一看代码变化了会重新生成一个序列化版本号,那么之前我们序列化过的对象就无法反序列化回来了,会报错:
java.io.InvalidClassException:
daydayup.day002.Student;
local class incompatible: stream classdesc serialVersionUID = 21338476354139427, local class serialVersionUID = 8715101302771642032
怎么解决这个缺点?
解决方案是,我们可以手动添加一个序列化版本号,这样在修改代码后,这个类仍然是这个类,不会产生上面的异常。当然了,要是改的面目全非,数据也不会显示的。
intellij idea中可以这么做
配置完之后就会有提示,按下 Alt+Enter即可生成。
//类似这样的效果
private static final long serialVersionUID = 21338476354139427L;
IO读取properties
public class IOPropertiesTest {
public static void main(String[] args) {
try (FileReader fileReader = new FileReader("src/main/resources/document/test.properties")) {
Properties properties = new Properties();
properties.load(fileReader);
String name = properties.getProperty("username");
String pw = properties.getProperty("password");
System.out.println(name);
System.out.println(pw);
} catch (FileNotFoundException fileNotFoundException) {
fileNotFoundException.printStackTrace();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
多线程
进程和线程
进程是一个应用程序,线程是进程中的执行单元。一个进程可以启动多个线程。
在Dos窗口执行’java HelloWorld’ 命令时,发生了什么?
先启动JVM,一个JVM就是一个进程。JVM启动一个线程调用main方法,同时再启动一个垃圾回收线程负责回收垃圾。也就是说,Java程序中至少也是两个线程并发。
调用线程最简单的三种方式
public class MultiThreadTest01 {
public static void main(String[] args) {
// 继承Thread,重写run方法
MyThread01 mt1 = new MyThread01();
//启动一个分支线程在JVM中开辟一个新的栈空间
// start() 会在开辟空间之后就结束,像一个导火索
// 与此同时
mt1.start();
//实现Runnable接口,利用线程包装
Thread mt2 = new Thread(new MyThread02());
//启动一个分支线程在JVM中开辟一个新的栈空间
// start() 会在开辟空间之后就结束,像一个导火索
// 与此同时
mt2.start();
//匿名内部类
Thread mt3 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("m3:" + i);
}
});
mt3.start();
for (int i = 0; i < 100; i++) {
System.out.println("main" + ":" + i);
}
}
}
class MyThread01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("m1" + ":" + i);
}
}
}
class MyThread02 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("m2" + ":" + i);
}
}
}
线程的生命周期
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
在主函数中用new出来的线程调用sleep() 方法,会让该线程休眠吗?
不会,仍然会让主函数所在线程休眠。
合理地终止线程
public class StopThreadTest {
public static void main(String[] args) {
Stop target = new Stop();
Thread t1 = new Thread(target);
t1.start();
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
target.flag = false;
}
}
class Stop implements Runnable {
boolean flag = true;
@Override
public void run() {
for (int i = 1; i < 100; i++) {
if (flag) {
System.out.println("开始输出:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("中止" + Thread.currentThread().getName() + "线程啦!");
return;
}
}
}
}
线程安全问题的条件
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
怎么解决线程安全问题?
排队执行线程,称为线程同步机制,实际上就是不让线程并发。
对于Java中的三种变量来说
局部变量:永远不会存在线程安全问题,疑问局部变量不共享,一个线程一个栈,可以有多个。
实例变量:在堆中,堆只1个。
静态变量:在方法区,方法区只有一个。
堆和方法区都是线程共享的,所以可能存在线程安全问题。
synchronized的三种写法:
-
同步代码块,灵活 synchronized(线程共享对象:实例变量或者静态变量){
}
-
在实例方法上使用synchronized,表示共享对象是this,同步代码块是整个方法体。 -
在静态方法上使用,表示共享类锁,类锁只有一把,就算创建100个对象,1000个线程分别访问不同的对象的这个静态方法,实际上相当于访问同一个对象。
避免线程安全问题的写法
尽量用局部变量代替“实例变量和静态变量”
在使用实例变量时,让不同的线程访问不同的对象。
实在不能避免的,只能使用synchronized关键字修饰了。
Object类中的wait()和notify() 方法
wait方法的作用:
Object o = new Object();
o.wait();
表示让正在o对象上活动的线程进入等待状态,无限期等待,直到被唤醒为止。也可以说,o.wait(); 的调用,会让 当前执行这行代码的线程 (也就是正在o上活动的线程)进入等待状态,并且释放之前被占有的o对象的锁。
而o.notify(); 的调用,会唤醒在o对象上等待的线程,让其继续执行。
|