------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
- 序列化流与反序列化流
ObjectOutputStream 对象输出流
writeObject(Object obj) 可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中
ObjectInputStream对象输入流
readObject(Objectobj) 从源输入流中读取字节序列,反序列化为一个对象并返回
序列化:将数据分解成字节流,以便存储在文件中或在网络上传输
反序列化:打开字节流并重构对象
主要用途:把对象的字节序列永久地保存到硬盘上,通常放在一个文件中;
在网络上传送对象的字节序列。
序列化的实现:将需要被序列化的类实现java.io.Serializable 接口(标记型接口,没有抽象方法),该接口没有需要实现的方法,implementsSerializable 只是为了标注该对象是可被序列化的。然后使用一个输出流(如FileOutputStream )来构造一个ObjectOutputStream(对象输出流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Objectobj)方法就可以将参数为obj的对象写出,要恢复的话则用输入流。
- 对象序列化的步骤:
- 创建一个对象输出流
- 通过对象输出流的 writeObject()方法写对象
对象序列化的特性:
1.static 成员不属于对象,属于自己的类,静态是对象共享数据,静态无法序列化
2.设计线程的类不能序列化
3.为保证序列化安全,可将敏感数据标记为 transient ,阻止成员变量序列化
表示序列化版本标识符的静态变量 serialVersionUID
Java运行时环境根据类的内部细节自动生成的
显示定义 serialVersionUID 的主要用途:
希望类的不同版本对序列化兼容;不希望类的不同版本对序列化兼容
- Properties 集合类
继承 Hashtable,实现 Map 接口
String getProperty(String key) // 用指定的键在此属性列表中搜索属性
Object setProperty(String key, String value) // 掉用Hashtable 的方法put
Set<String> stringPropertyNames() // 返回此属性列表中的键集
可以和IO流结合使用
public void load(Reader reader)
// 传递字节输入流或字符输入流,从流中直接加载键值对
public void store(Writer writer, String comments)
// 将集合中的键值对,通过流保存回文件中
// 创建字节输入流
FileInputStreamfis= newFileInputStream("C:\\properties.txt");
// 创建对象
Propertiespro= newProperties();
// 从输入流中加载键值对
pro.load(fis);
// 关闭输入流
fis.close();
// 判断键是否存在
if (pro.getProperty("lisi") != null) {
// 创建字节输出流
FileOutputStreamfos= newFileOutputStream("C:\\properties.txt");
// 设置键对应的值
pro.setProperty("lisi", "100");
// 将键值对重新保存回文件
pro.store(fos, "");
// 关闭输出流
fos.close();
- 多线程
进程是OS 中运行的一个任务,一个应用程序运行在一个进程中。
进程中所包含的一个或多个执行单元称为线程。一个线程是进程的一个顺序执行流。
多线程是一种机制,它允许在程序中并发执行多个指令流,每个指令流都成为一个线程,彼此间互相独立。
- 实现多线程的三种方式:
- 继承 java.lang.Thread 类
在创建Thread类的子类中重写 run() 方法,加入线程所要执行的代码
new 创建一个线程子类的实例出来,并调用 start() 方法启动线程
2. 实现 java.lang.Runnable 接口
声明自己的类实现 Runnable 接口并实现接口中的run()方法,将线程代码写入其中
(好处:将线程与线程要执行的任务分离开减少耦合,适合让线程对象中的数据共享;
Java是单继承的,定义一个类实现Runnable接口可以避免由于单继承带来的局限性)
3. 实现java.util.concurrent.Callable<V> 接口
声明自己的类实现 Callable 接口并实现接口中的call()方法,将线程代码写入其中
调用线程池的静态方法获取线程池对象
线程池管理对象提交一个接口的实现类对象用于执行
获取到线程运行结束后的返回值结果Future
调用返回值结果对象的get方法获取结果
(可以有返回值,可以抛出异常,但代码比较复杂,一般不用)
-----------------------extends Thread 实现多线程----------------------
// 获取当前线程名称
Thread.currentThread().getName()
// 定义线程子类继承Thread,重写run()方法
class MyThread extends Thread {
/*
* 如果想要在线程子类创建对象时传递参数设置线程名,
* 必须重写父类Thread类中的带参数的构造方法,super调用
*/
public MyThread(String string) {
super(string);
}
@Override
publicvoid run() {
for (inti = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+ "\t"+ i);
}
}
}
// 创建线程子类的对象
MyThread myThread = new MyThread("呵呵");
// 设置线程名称
//myThread.setName("myThread");
MyThread myThread2 = new MyThread("呵呵2");
//myThread2.setName("myThread2");
MyThread myThread3 = new MyThread("呵呵3");
//myThread3.setName("myThread3");
// 调用 start 方法启动线程
myThread.start();
// 不能多次启动同一线程
// myThread.start(); // java.lang.IllegalThreadStateException
myThread2.start();
myThread3.start();
--------------------implements Runnable 实现多线程--------------------
// 声明类实现Runnable接口,实现run()方法
class MyRunnable implements Runnable {
@Override
publicvoid run() {
for (inti = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+ "\t"+ i);
}
}
}
// 创建实现类对象
MyRunnable target = new MyRunnable();
// 创建线程对象传递实现类对象
// 构造方法的参数可设置线程名
Thread thread = new Thread(target, "thread");
Thread thread2 = new Thread(target, "thread2");
Thread thread3 = new Thread(target, "thread3");
// 调用 start 方法启动线程
thread.start();
thread2.start();
thread3.start();
-------------------implements Callable<V> 实现多线程------------------
// 声明类实现Callable接口,实现call()方法
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (inti = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+ "\t"+ i);
}
returnnull;
}
}
// Executors类的静态方法获取线程池对象
// 创建一个使用单个工作线程的Executor(线程池)
ExecutorServicees = Executors.newSingleThreadExecutor();
// 创建一个可重用固定线程数的线程池
// ExecutorServicees2 = Executors.newFixedThreadPool(2);
// 创建一个可根据需要创建新线程的线程池
// ExecutorServicees3 = Executors.newCachedThreadPool();
// 线程管理对象,提交一个返回值的任务(接口的实现类对象)用于执行
// 返回一个表示任务的未决结果的Future(异步计算的结果)
Future<String>future= es.submit(new MyCallable());
// Future<String>future = es2.submit(new MyCallable());
// Future<String>future2 = es2.submit(new MyCallable());
// 调用Future接口的get方法获取结果
Stringstring= future.get();
// Stringstring2 = future2.get();
System.out.println(string);
--------------------------实现多线程的三种方式------------------------
线程调度
Java 使用的是抢占式调度模型,优先级高的线程先执行
设置和获取线程优先级,线程的优先级有继承关系
public final intgetPriority()
public finalsetPriority(int newPriority)
优先级的范围:1~10,低 -->高
static intMAX_PRIORITY 最高优先级10
static intMIN_PRIORITY 最低优先级1
static intNORM_PRIORITY 默认优先级5
主线程的默认优先级为Thread.NORM_PRIORITY
常用线程操作 API
Thread Thread.currentThread() // 获取运行当前代码片段的线程
long getId() // 返回该线程的标识符
String getName() // 返回该线程的名称
Thread.currentThread().getName() // 获取当前线程名称
void setName(String name) // 设置线程名称
Thread(String name) // 构造方法,创建线程时设置名称
int getPriority() // 获取线程优先级
void setPriority(int priority) // 设置线程优先级
boolean isDaemon() // 判断是否为守护线程
void setDaemon(boolean b) // 当参数为 true 设置为守护线程
(守护线程:当进程中只剩下守护线程时,所有守护线程强制终止。)
static void sleep(long ms) // 使线程进入阻塞状态指定毫秒值
static void yield() // 主动退让当次 CPU 时间片
void join() // 等待当前线程结束
线程的5种状态
阻塞状态的进入方式及对应的恢复方法:
进入 恢复
睡眠(sleep) 自动恢复
挂起(suspend) 恢复(resume)
等待(wait) 通知(notify)
- 线程同步
多个线程并发读写同一个临界资源时会发生“线程并发安全问题”。
常见的临界资源:多线程共享实例变量,多线程共享静态公共变量。
若想解决线程安全问题,需要将异步的操作变为同步操作。
- 同步代码块
同步代码块包含两部分:一个作为锁的对象的引用,一个作为由这个锁保护的代码块。
synchronized(同步监视器——锁对象引用) {
// 需要同步的代码块
}
若方法所有代码都需要同步也可以给方法直接加锁,就成为同步方法。
每个Java对象都可以用做一个实现同步的锁,线程进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,而且无论是通过正常途径退出还是通过异常退出都一样,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块和或方法。
选择合适的锁对象:使用 synchronized 需要对一个对象上锁以保证线程同步。那么这个锁对象应当注意:
-- 多个需要同步的线程在访问该同步块时,看到的应该是同一个锁对象引用。
-- 通常使用 this 来作为锁对象。
选择合适的锁范围:在使用同步块时,应当尽量在允许的情况下减少同步范围,以提高并发的执行效率。
- 同步方法
静态方法锁:publicsynchronized static void xxx() {…}
该方法锁的对象是类对象。每个类都有唯一的一个类对象:类名.class。
如果静态方法与非静态方法同时声明了synchronized,他们之间是非互斥关系的。原因在于,静态方法锁的是类对象,而非静态方法锁的是当前方法所属对象。
锁对象的选择:
如果锁对象是 this,就可以考虑使用同步方法;
否则能使用同步代码块的尽量使用同步代码块。
Lock 接口java.util.concurrent.locks.Lock
since JDK1.5 替换同步技术,接口的实现类是ReentrantLock(可重入、互斥的锁)
synchronized 和 Lock 的异同:
相同点:都可以实现线程同步
不同点:Lock 更精确的线程语义和更好的性能。
synchronized 会自动释放锁,而 Lock 需要手工在finally语句中释放。
- 死锁问题与线程间通信
死锁问题:两个或多个线程在执行过程中因争夺资源产生的一种互相等待的现象。
--------------------------同步代码块的嵌套案例---------------------------
package cn.itcast;
/*
* 线程死锁案例
* 多个线程,争夺同一个锁(相同的资源),出现相互等待的现象
*/
publicclassThreadDeadDemo {
publicstaticvoid main(String[] args) {
RunnableDeadd1 = new RunnableDead(true);
RunnableDeadd2 = new RunnableDead(false);
Threadt1 = new Thread(d1);
Threadt2 = new Thread(d2);
t1.start();
t2.start();
}
}
class RunnableDead implements Runnable {
privatebooleanb; // 设置标志位
public RunnableDead(booleanb) {
this.b = b;
}
@Override
publicvoid run() {
while (true) {
if (b) { // b==true,线程先获取A锁,同步代码块的锁是LockA对象
synchronized (LockA.locka) {
System.out.println("if...locka");
// 线程没有出去A锁,进入B锁,同步代码块的锁是LockB对象
synchronized (LockB.lockb) {
System.out.println("if...lockb");
}
}
}else{ // b==false,线程先获取B锁,同步代码块的锁是LockB对象
synchronized (LockB.lockb) {
System.out.println("else...lockb");
// 线程没有出去B锁,进入A锁,同步代码块的锁是LockA对象
synchronized (LockA.locka) {
System.out.println("else...locka");
}
}
}
}// endwhile
}// endrun
}
// 创建两个对象锁,且都是唯一的,只能通过类的静态成员获取对象当锁
class LockA {
publicstaticfinal LockA locka = new LockA();
private LockA() {
}
}
class LockB {
publicstaticfinal LockB lockb = new LockB();
private LockB() {
}
}
线程间通信:通过设置线程(生产者)和获取线程(消费者)针对同一个对象进行操作
wait 和 notify :多线程之间需要协调工作。如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于 wait/notify。等待机制与锁机制是密切联系的。
IllegalMonitorStateException无效的监视器状态异常
执行方法wait/notify时,缺少所对象,没有锁的情况下,不能使用等待遇唤醒机制,必须有锁的支持才能使用。无论等待还是唤醒,都必须在本锁内操作,唤醒必须同一把锁的线程,只能作为锁的对象,才能调用方法 wait notify
线程间通信的代码改进:
---------------------通过等待唤醒机制实现数据依次出现---------------------
package cn.itcast;
/*
* 线程间通信
* 通过等待唤醒机制和设置修改标志位实现数据依次出现
*/
publicclassThreadComnDemo {
publicstaticvoid main(String[] args) {
Students = new Student();
Producerp = new Producer(s);
Consumerc = new Consumer(s);
new Thread(p).start();
new Thread(c).start();
}
}
class Student {
Stringname;
Stringsex;
booleanflag;
}
class Producer implements Runnable {
private Student s;
public Producer(Student s) {
this.s = s;
}
@Override
publicvoid run() {
intx = 0;
while (true) {
synchronized (s) {
// 对成员标记判断,如果true,赋值完成,等待
if (s.flag){
try {
s.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0) {
s.name = "张三";
s.sex = "男";
}else{
s.name = "李四";
s.sex = "女";
}
x++;
// 改标记
s.flag = true;
// 唤醒对方线程
s.notify();
}
}
}
}
class Consumer implements Runnable {
private Student s;
public Consumer(Student s) {
this.s = s;
}
@Override
publicvoid run() {
while (true) {
synchronized (s) {
// 对成员进行判断,如果false,打印完成,等待
if (!s.flag) {
try {
s.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + " " + s.sex);
// 改标记
s.flag = false;
// 唤醒对方线程
s.notify();
}
}
}
}
----------------------把同步代码块改进为同步方法实现----------------------
package cn.itcast;
/*
* 线程间通信
* 同步技术改进,把同步代码块改进为同步方法实现
*/
publicclassThreadComnDemo2 {
publicstaticvoid main(String[] args) {
Students = new Student();
new Thread(new Producer(s)).start();
new Thread(new Consumer(s)).start();
}
}
class Student {
private String name;
private String sex;
privatebooleanflag; // 设置标志位
// 对私有成员,提供公共访问方式,对2个成员变量进行赋值
publicsynchronizedvoid set(String name, String sex) {
// 判断标记,如果true,等待,不能赋值
if (flag) {
try {
this.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
publicsynchronizedvoid get() {
// 判断标记,如果false,等待,不能打印
if (!flag) {
try {
this.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + " " + sex);
flag = false;
this.notify();
}
}
// 生产者线程
class Producer implements Runnable {
private Student s;
public Producer(Student s) {
this.s = s;
}
@Override
publicvoid run() {
intx = 0;
while (true) {
if (x % 2 == 0) {
s.set("张三", "男");
}else{
s.set("李四", "女");
}
x++;
}
}
}
// 消费者线程
class Consumer implements Runnable {
private Student s;
public Consumer(Student s) {
this.s = s;
}
@Override
publicvoid run() {
while (true) {
s.get();
}
}
}
-----------------------匿名内部类方式实现多线程-----------------------
new Thread(new Runnable() {
publicvoid run() {
System.out.println("匿名内部类实现多线程");
}
}).start();
new Thread() {
publicvoid run() {
System.out.println("多线程匿名内部类");
};
}.start();
- 启动一个线程是用run()还是start()?
调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行,但并不意味着线程就会立即执行。
run()方法可以产生必须退出的标志来停止一个线程。
- sleep()和wait() 有什么区别?
sleep方法是Thread类的静态方法,导致此线程暂停执行指定时间,把执行机会给其它线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
wait方法是Object类的非静态方法,对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法后本线程才进入对象锁定池准备获得对象锁进入运行状态。
- 为什么 wait(),notify(), notifyAll()等方法都定义在 Object类中?
线程控制方法不定义在 Thread 类中,原因是锁的问题造成的。
锁肯定是不确定的对象,而线程控制方法必须是锁对象才能调用,因此线程控制方法定义在 Object类中,保证所有对象都可以调用。