Day 29 学习分享 - 序列化流和线程基础概述

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 = 1L;
     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);	// zhangsan
        System.out.println("Address: " + e.address); // beiqinglu
        System.out.println("age: " + e.age); // 0
    }
}
	当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()方法来启动该线程
/**
 * 继承Thread,重写父类的run()方法
 */
public class MyThread extends Thread {

    /*
     * 利用继承中的特点
     * 将线程名称传递 进行设置
     */
    public MyThread(String name) {
        super(name);
    }

    /*
     * 重写run方法
     * 定义线程要执行的代码
     */
    public void run() {
        for (int i = 0; i < 20; i++) {
            //getName()方法 来自父亲
            System.out.println(getName() + i);
        }
    }
}
/**
 * 继承Thread,重写父类的run()方法
 */
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()方法来启动线程。
/**
 * 实现Runnable接口,重写run方法
 */
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}
/**
 * 实现Runnable接口,重写run方法
 */
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
/*
 * 当在主线程当中执行到t1.join()方法时,就认为主线程应该把执行权让给t1
 */
public class JoinThread extends Thread{

    // 提供一个有参数的构造器,用于设置该线程的名字
    public JoinThread(String name) {
        super(name);
    }

    // 重写run方法,定义线程执行体
    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();
                // main线程调用了jt线程的join()方法,main线程
                // 必须等jt执行结束才会向下执行
                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()方法处暂停.
/**
 * sleep方法
 */
public class SleepTest {
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println("当前时间: " + new Date());
            // 调用sleep方法让当前线程暂停1s。
            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()方法来控制并发线程的执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值