61.对象流ObjectOutputStream与ObjectInputStream
将java对象存储到文件中,将java中的对象保存下来的过程我们称之为序列化(Serialize);
将硬盘中的数据恢复到内存中,恢复成java对象,我们称之为反序列化(DeSerialize);
序列化:
public static void test20() throws Exception {
Movie m = new Movie();
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("C:\\Users\\Administrator\\Desktop\\临时\\dos"));
oos.writeObject(m);
oos.flush();
oos.close();
}
反序列化:
public static void test21() throws Exception {
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("C:\\Users\\Administrator\\Desktop\\临时\\dos"));
Object m = ois.readObject();
System.out.println(m);
ois.close();
}
一次序列化多个对象:
ArrayList与集合中的元素都要实现Serializable接口;
参与序列号与反序列化的类必须实现Serializable接口;
public static void test22() throws FileNotFoundException, IOException {
// 将对象放到集合当中,序列化集合
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("C:\\Users\\Administrator\\Desktop\\临时\\oos"));
List<Movie> ls = new ArrayList<>();
ls.add(new Movie(12.2, "神秘代码"));
ls.add(new Movie(22.2, "后天"));
ls.add(new Movie(33.18, "2012"));
oos.writeObject(ls);
oos.flush();
oos.close();
}
反序列化集合:
public static void test23() throws Exception {
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("C:\\Users\\Administrator\\Desktop\\临时\\oos"));
List<Movie> ls = (List<Movie>) ois.readObject();
for (Movie movie : ls) {
System.out.println(movie);
}
}
public class Movie implements Comparable<Movie>, Serializable {}
通过查看源代码发现,Serializable接口是一个标志接口,里面没有任何内容;这个标志性接口起到一个标志的作用,java虚拟机看到一个类实现了这个接口,就会为该类自动生成一个 序列化版本号;
public interface Serializable {
}
如果希望类的某个属性不参与序列化可以用transient修饰,表示游离的,不参与序列化
transient double price;
java语言是通过两种方式区分类的:
①通过类名区分,如果类名不一样,那么两个类肯定是不同的;
②如果类名一样,通过序列化版本号区分;
如果两个人都写了同一个类,他们的类名称相同,都实现了序列化,那么这个时候java虚拟机就可以通过序列化区分这两个类;
自动生成序列化版本号的缺点:一个实现了序列化的类,一旦写入文件后,如果后续修改了类的源代码,编译后就会生成新的序列化版本号,此时这个类需要重新写入文件,否则这个类的反序列化就会报错;
优化:一个类一旦实现了Serializable接口,建议给该类提供一个固定不变的版本号,这样一旦类发生了修改,版本序列化不变,java虚拟机会认为是同一个类,不用重新将这个类写入文件,反序列化也仍然能运行;
建议将序列化版本号手动写出来,不自动生成;
手动写序列号后,两个类名相同,序列化版本号又相同的情况概率极低,可以忽略;
public class Movie implements Comparable<Movie>, Serializable {
// transient游离的,不参与序列化
double price;
String mName;
String mDirector;
String mPlayer;
private static final long serialVersionUID = 354646846846L;
}
62.IO与Properties联合运用
经常改变的数据,可以写到文件中,方便程序动态读取,例如将数据库链接写到文件中,修改的时候只需要修改文件,这样子不需要修改java源代码,不需要重新编译,不需要重启服务器,就可以动态拿到信息;
这种文件就称为配置文件;
当配置文件的格式是:
username=root
pwd=root
# #号是注释
#属性配置文件key重复的话,value会自动覆盖;
pwd=root123
#key与value直接最好不要有空格
#建议key和value直接用=号形式,key=value,不建议用冒号
port:3306
这种格式时,我们把这种文件称为属性配置文件;
java规范中要求,属性配置文件以.properties结尾,但这不是必须的;
这种以.properties结尾的文件称为属性配置文件;
Properties是专门存放属性配置文件内容的一个类;
public static void test24() throws Exception {
FileReader fr = new FileReader("C:\\Users\\Administrator\\Desktop\\临时\\config.txt");
Properties p = new Properties();
p.load(fr);
System.out.println(p.getProperty("username"));
System.out.println(p.getProperty("pwd"));
}
63.多线程
进程是一个应用程序(一个软件);
线程是一个进程中的执行场景/执行单元;
一个进程可以有多个线程;
对于java程序来说,当在dos命令窗口输入:java hello world,回车之后,会先启动JVM,JVM就是一个进程,JVM会先启动一个主线程调用main方法,再启动一个垃圾回收线程负责看护,回收垃圾,现在的java程序中至少有两个线程并发,一个垃圾回收线程,一个是执行main方法的主线程;
进程之间的内存独立不共享;
在java语言中,同一个进程内的线程之间的堆内存和方法区内存共享,但栈内存独立,一个线程一个栈;
假设启动10个线程,会有10个栈空间,每个栈之间互不干扰,各自独立执行,这就是多线程并发;
线程类似于火车站的售票口,多个售票口独立卖票,所以多线程并发可以提高效率;
java之所以有多线程机制,就是为了提高程序的处理效率;
使用了多线程机制之后,main方法结束了,程序有可能还没有结束,main方法结束只是主线程结束了,主栈空了,其他栈可能还在运行;
真正的多线程并发:t1线程执行t1的,t2线程执行t2的,t1不会影响t2,t2也不会影响t1;
对于多核的CPU电脑来说,真正的多线程并发是没问题的;4核CPU表示在同一个时间点上,可以真正的有4个进程并发执行;
单核的CPU只有一个大脑,不能做到真正的多线程并发,但是可以做到给人一种多线程并发的感觉;
对于单核的CPU来说,在某一个时间点上,实际上只能处理一件事情,但由于CPU处理速度极快,多个线程之间频繁切换执行,给人的感觉是同时在做;比如播放音乐于与播放视频,CPU实际上是在这两个进程之间频繁切换的,音乐与视频一直在播放,给人的感觉是同时进行的;
java中实现多线程有2种方式:
①一个类直接继承java.lang,Thread,并重写run方法;
②A类实现Runnable接口,并重写run方法,new Thread对象传入A类的实例,线程对象调用start方法;
第二种方式更为实用,因为一个类实现了接口还可以继承其他类,更灵活;
64.继承Thread启动多线程
public class TestThread extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
//这段代码运行在分支线程中
for (int i = 0; i < 200; i++) {
System.out.println("支线程i-->" + i);
}
}
}
public class App {
public static void main(String[] args) throws Exception {
// main方法,这里的代码属于主线程,在主栈中运行
// 新建一个分支线程对象
TestThread tt = new TestThread();
// tt.run(); //对象调用不会启动线程,不会分配新的分支栈,这种方式是单线程
// 启动线程
/*
* start方法的作用是启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码完成后会瞬间结束
* 这段代码的作用只是为了开启一个新的栈空间,只要新的栈空间开出来,start方法就结束了,线程就启动成功了
* 启动成功的线程会自动调用run方法,并且run方法会在分支栈的栈底部(压栈)
* run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的
*/
tt.start();
// 这段代码运行在主线程中
for (int i = 0; i < 200; i++) {
System.out.println("主线程i-->" + i);
}
}
}
65.实现Runnable启动多线程
package Thread;
public class TestThreable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 200; i++) {
System.out.println("ruanble支线程i-->" + i);
}
}
}
public static void test26() {
//创建线程对象
Thread t = new Thread(new TestThreable());
//启动多线程
t.start();
for (int i = 0; i < 200; i++) {
System.out.println("主线程i-->" + i);
}
}
66.线程的生命周期
67.线程的相关方法
设置与获取线程名字:
Thread t = new Thread(new TestThreable());
Thread t2 = new Thread(new TestThreable());
//设置线程名字
t2.setName("aasd");
//获取线程名字
System.out.println(t.getName());
System.out.println(t2.getName());
当没有给线程设置名字的时候,线程的默认名字是 Thread-0,Thread-1...
public static void main(String[] args) throws Exception {
Thread t = Thread.currentThread();
//获取当前线程的名字, 在main方法中执行,获取的就是主线程
System.out.println(t.getName());
test26();
}
获取当前线程:
public static void main(String[] args) throws Exception {
Thread t = Thread.currentThread();
// 获取当前线程的名字, 在main方法中执行,获取的就是主线程
System.out.println(t.getName());
test26();
}
public static void test26() {
// 创建线程对象
Thread t = new Thread(new TestThreable());
Thread t2 = new Thread(new TestThreable());
t.setName("支线程007");
t2.setName("X线程XXX");
// 启动多线程
t.start();
t2.start();
}
@Override
public void run() {
Thread t = Thread.currentThread();
String n = t.getName();
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
System.out.println(n + "i-->" + i);
}
}
68.sleep方法
static void sleep(long mis)
作用:让当前线程进入休眠,进入阻塞状态,放弃占有CPU时间片,让给其他线程使用;
// 当前线程睡眠3秒
Thread.sleep(3000);
System.out.println("helll");
sleep方法可以实现一段程序几秒后运行,或者每隔几秒运行一次;
中断睡眠:
public static void test26() {
Thread t = new Thread(new TestThreable());
// 启动多线程
t.start();
// 中断t线程的睡眠,这种中断睡眠的方法依靠java的异常处理机制
t.interrupt();
}
public class TestThreable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 30; i++) {
System.out.println("i-->" + i);
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
69.终止线程
stop:
public static void test26() {
Thread t = new Thread(new TestThreable());
// 启动多线程
t.start();
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 终止线程,已过时,不建议使用
t.stop();
}
public class TestThreable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 30; i++) {
System.out.println("i-->" + i);
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
缺点:容易丢失数据,因为这种方式直接将线程杀死,线程没有保存的数据容易丢失,不建议使用;
合理的终止一个线程:
在线程类内部设置一个flag即可
public static void test26() {
// 创建线程对象
TestThreable tt = new TestThreable();
Thread t = new Thread(tt);
// 启动多线程
t.start();
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 终止线程,已过时,不建议使用
tt.flag = false;
System.out.println("sssssssssss");
}
public class TestThreable implements Runnable {
public boolean flag = true;
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 30; i++) {
if (flag) {
System.out.println("支线程i-->" + i);
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
return;
}
}
}
}
70.线程调度
常见的线程调度模型:
抢占式调度模型:哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些;JAVA使用的就是抢占式调度模型;
均分式调度模型:平均分配CPU时间片,每个线程占用的CPU时间片时间长度一样,平均分配;有一些语言线程调度模型采用的就是这种方式;
相关方法:
int getPriority():获取线程优先级;
void setPriority(int newPriority):设置线程优先级;
最大优先级:10 (优先级高的获取的CPU时间片可能会多一些,但也不完全是,大概率是多的)
最小优先级:1
默认优先级:5
static void yield():暂停当前的线程对象,并执行其他线程;
yield方法不是阻塞方法,让当前线程让我,让给其他线程使用,yield方法的执行会让当前线程从运行状态回到就绪状态,回到就绪之后有可能还会再抢到;
void join():当前线程进入阻塞状态,等待调用这个方法的线程结束,再继续执行当前线程;
//yield()方法
public static void test26() {
// 创建线程对象
TestThreable tt = new TestThreable();
Thread t = new Thread(tt);
// 启动多线程
t.start();
for (int i = 0; i < 200; i++) {
if (i % 10 == 0) {
Thread.yield();
}
System.out.println("主线程i-->" + i);
}
}
//join方法
public static void test26() {
// 创建线程对象
TestThreable tt = new TestThreable();
Thread t = new Thread(tt);
// 启动多线程
t.start();
for (int i = 1; i < 100; i++) {
if (i % 10 == 0) {
try {
t.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("主线程i-->" + i);
}
}