1.对象流
ObjectOutputStream
ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。
可以使用 ObjectInputStream 读取(重构)对象。
ObjectInputStream
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
序列化(持久化) 和 反序列化
持久化:就是将内存中的对象 存储到硬盘上
反序列化: 就是将硬盘文件中的对象 再读入到内存中
java对象流 简单来说 就是直接把Object对象 进行 写入 或 读出
功能:
方便对象在硬盘上的存储
例如:
学生信息
Student stu = new Student();
持久化 使用 ObjectOutputStream 将对象的属性信息 存储到指定的硬盘文件上
反序列化 使用 ObjectInputStream 将硬盘上存储的对象信息 读到内存中 方便使用
注意:
想要让对象被序列化 或者 反序列化 必须让该对象所属的类 实现 Serializable接口
否则 在进行 序列化时出现 NotSerializableException异常
/*
* public interface Serializable
* 类通过实现 java.io.Serializable 接口以启用其序列化功能。
* 未实现此接口的类将无法使其任何状态序列化或反序列化。
* */
2.线程
2.1 线程的概念
注意:
线程中的运行结果 如果出现你所不能接受的现实都是可能的
(多核cpu的影响)
什么是程序?
就是一段代码 是一组指令的有序集合
它本身 没有任何 运行 的含义
什么是进程?
进程是程序的一次运行状态
它对应着 从代码加载 --> 执行 --> 执行完毕的整个过程
它是有自己的生命周期
它因创建而产生
因调度而执行
因等待资源而处于等待等待状态
因任务结束而结束
什么是线程?
线程就是进程里的一个控制单元 是线程在控制进程
每个进程里面都包含若干个线程
一个进程至少由一个线程组成
线程 是 执行程序的一条路径
多线程并发 执行 可以提高程序的效率 可以同时完成多项任务
并发 和 并行:
并发:
就是两个任务同时请求运行
而处理器一次只能接收一个任务 就将两个任务 安排轮流执行
由于cpu时间片运行时 时间比较快 所以感觉两个程序在同时运行
操作一台电脑 分别跟两个人聊天
并行:
就是两个任务同时运行
甲任务执行的同时 乙任务也在执行
双核
两个手 操作两台电脑 分别跟两个人聊天
JVM启动的时候 是多线程的
至少启动了两根线程
一个是主线程(主线程会调用main方法)
一个是垃圾回收线程
2.2 线程的创建方式
继承Thread类
线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
/*
* 发现运行结果每次都不同
* 因为多个线程都在抢夺cpu的执行权 cpu执行到谁 就运行谁
* 明确一点 在某一个时刻 只能有一个线程在运行(多核除外)
* cpu在做着快速的切换 已达到看上去同时运行的效果
* 我们可以形象的把线程的运行 视为在互相抢夺cpu的执行权
* 这也就是多线程的一个特性 随机性 谁抢到 就算谁的 至于执行多长时间 由cpu说了
*
* run()方法 和 start()方法的区别
* Thread类用于描述线程
* 该类定义了一个功能 用于存储线程要运行的代码 该存储功能就是run方法
* 主线程 要运行代码 jvm默认规定 是main方法中的内容
* 我们自己创建的线程 需要运行的内容 一定要放在run方法中
* start()方法是用来启动线程的
* */
获取线程名称 Thread.currentThread().getName()
/*
* 创建线程的方式一
* 1.继承Thread类
* 2.重写run方法 将线程要执行的代码 放在run方法中
* 3.创建对象 调用start方法 启动线程 使该线程开始执行 java虚拟机调用该线程的run方法
* */
实现Runnable接口
/*
* 创建线程的方式二
* 创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。
* 然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
* 1.实现Runnable接口
* 2.重写Runnable接口中的run方法
* 3.通过Thread类 建立线程对象
* 将Runnable接口的子类对象作为参数传递到Thread的构造方法中
* 4.调用Thread类的start方法 启动线程
* 会调用Runnable子类的run方法
* */
******* 真正的线程 是Thread类 或者其子类
继承Thread类 和 实现Runnable接口的区别
从扩展的角度而言 最好使用 实现Runnable接口方式
如果确定只有一个线程的调用就使用继承
如果需要在继承一个类的同时 还能成为线程 就用实现
实现Callable接口
返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
V call()
计算结果,如果无法计算结果,则抛出一个异常。
1.实现Callable接口 同时定义泛型 该泛型为call方法的返回值类型
2.重写call方法 线程要运行的代码
3.创建FutureTask对象 将Callable接口的实现类进行转换
4.创建Thread类 传入FutureTask对象
5.选择性调用FutureTask的get方法 获取call方法的返回值
Callable产生结果
FutureTask获取结果
Runnable 和 Callable的区别?
相同之处:
1.都是接口 都是可以用来创建要成为线程的对象
2.Callable 和 Runnable 都可以用于 线程池中
不同之处:
1.Callable 的call方法可以返回一个类型
Runnable 的run方法没有明确的返回值 void
2.Callable 的call方法可以抛出异常
Runnable 的run方法不行
3.Callable 是jdk1.5之后加上的
Runnable 是jdk1.0就有
2.3 线程安全问题
同步 synchronized 线程锁
* 问题 发现线程安全性出现了隐患
* 当多条线程 操作一个共享数据时 一个线程语句执行了一部分 还没执行完
* 另一个线程参与进来 抢夺了cpu的执行权 造成共享数据的错误
* 解决方法
* 对多条线程共享数据 只能一个线程执行完之后 再执行另一个线程
* java对于多线程安全问题 提供了专业的解决方案
* 同步 synchronized
同步的三种方式:
同步的前提:
1.必须是两个 或者 两个以上的线程操作同一个资源
2.必须是多个线程使用同一个锁 确保锁 的唯一性
1.同步代码块
synchronized(锁){
要同步的代码
}
使用synchronized关键字 加上一个锁
同步代码块的锁 可以是 任意对象
2.同步方法
访问权限修饰符 synchronized 返回值类型 方法名(参数列表){
要同步的代码
}
同步方法的锁 是 this关键字
3.静态同步方法
访问权限修饰符 static synchronized 返回值类型 方法名(参数列表){
要同步的代码
}
静态同步方法的锁 是 类名.class 就是 类的字节码文件对象
同步的好处和弊端:
好处:解决了多线程并发访问的安全问题
弊端:多了一个判断锁的动作 消耗了内存 程序稍微变慢
以前的线程安全相关类回顾:
线程安全
Vector HashTable StringBuffer
线程不安全
ArrayList HashMap StringBuilder
Collections工具类中 定义了方法 可以将线程不安全 变成线程安全的
static <T> Collection<T>
synchronizedCollection(Collection<T> c)
返回指定 collection 支持的同步(线程安全的)collection。
static <T> List<T>
synchronizedList(List<T> list)
返回指定列表支持的同步(线程安全的)列表。
static <K,V> Map<K,V>
synchronizedMap(Map<K,V> m)
返回由指定映射支持的同步(线程安全的)映射。
static <T> Set<T>
synchronizedSet(Set<T> s)
返回指定 set 支持的同步(线程安全的)set。
Reentrantlock 重入锁
通过显性定义同步锁对象来实现同步
一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,
但功能更强大。
提供了更广泛的锁定操作
lock() 锁
unlock() 解锁
注意:这两个方法 需要同时使用 相当于 同步代码块的花括号开始 和 结束
synchronized(){
被同步的内容
}
区别:
1.实现的区别
synchronized 是依赖于 JVM实现的
是操作系统来控制实现
ReentrantLock 是JDK实现的
是自己敲代码实现的 加锁和解锁
2.性能区别
synchronized 性能略差
ReentrantLock 性能稍强
3.功能区别
synchronized 使用比较简洁 由编译器去加锁和解锁
ReentrantLock 需要手动声明 来加锁 和 解锁
死锁(了解)
在多线程同步的时候 如果同步代码块发生嵌套
使用了相同的锁 就有可能出现死锁的情况
如何避免死锁:
尽量不要嵌套使用同步 synchronized
案例:
筷子
你有一只筷子 我有一只筷子
要吃饭
你不给我 我也不给你
僵持不下
死锁