一.序列化的实现方式
概念:序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
目的:1、以某种存储形式使自定义对象持久化; 2、将对象从一个地方传递到另一个地方。
实现方法:
1:被序列化的类要实现 java.io.Serializable 接口,序列化时,需要用到对象输出流ObjectOutputStream ,然后通过文件输出流构造 ObjectOutputStream 对象调用writeObject写入到文件 ,反之,反序列化时用到对象输入流ObjectIntputStream, 然后通过文件输出流构造 ObjectIntputStream对象调用readObject加载到内存,注意,是返回万能Object类型
注意:序列化时transient变量(这个关键字的作用就是告知JAVA我不可以被序列化)和静态变量不会被序列化(关于静态变量不会序列化保留意见,本实例先不用静态变量) ,也是最应该注意的,如果你先序列化对象A后序列化B,那么在反序列化的时候一定记着JAVA规定先读到的对象是先被序列化的对象,不要先接收对象B,那样会报错。
序列化
//创建一个对象输出流,将对象输出到文件
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(fileName));
out.writeObject("序列化日期是:"); //序列化一个字符串到文件
out.writeObject(new Date()); //序列化一个当前日期对象到文件
UserInfo user=new UserInfo("renyanwei","888888",20);
out.writeObject(user); //序列化一个会员对象
out.close();
反序列化
//创建一个对象输入流,从文件读取对象
ObjectInputStream in=new ObjectInputStream(new FileInputStream(fileName));
//注意读对象时必须按照序列化对象顺序读,否则会出错
//读取字符串
String today=(String)(in.readObject());
System.out.println(today);
//读取日期对象
Date date=(Date)(in.readObject());
System.out.println(date.toString());
//读取UserInfo对象并调用它的toString()方法
UserInfo user=(UserInfo)(in.readObject());
System.out.println(user.toString());
in.close();
2: 被序列化的类要实现java.io.Externalizable接口,Externalizable 接口定义了两个方法,writerExternal方法在序列化时被调用,可以再该方法中控制序列化内容,readExternal方法在反序列时被调用,可以在该方法中控制反序列的内容。
//当序列化对象时,该方法自动调用
public void writeExternal(ObjectOutput out) throws IOException{
System.out.println("现在执行序列化方法");
//可以在序列化时写非自身的变量
Date d=new Date();
out.writeObject(d);
//只序列化userName,userPass变量
out.writeObject(userName);
out.writeObject(userPass);
}
//当反序列化对象时,该方法自动调用
public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException{
System.out.println("现在执行反序列化方法");
Date d=(Date)in.readObject();
System.out.println(d);
this.userName=(String)in.readObject();
this.userPass=(String)in.readObject();
}
序列化
//创建一个对象输出流,讲对象输出到文件
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(fileName));
UserInfo user=new UserInfo("renyanwei","888888",20);
out.writeObject(user); //序列化一个会员对象
out.close();
反序列化
//创建一个对象输入流,从文件读取对象
ObjectInputStream in=new ObjectInputStream(new FileInputStream(fileName));
//读取UserInfo对象并调用它的toString()方法
UserInfo user=(UserInfo)(in.readObject());
System.out.println(user.toString());
in.close();
二.基本的动态代理
概念:所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。
实现方法:
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;
//动态代理类(需要继承自InvocationHandler )
public class HelloHandler implements InvocationHandler {
private Object proxyed;
public HelloHandler() {
}
public HelloHandler(Object proxyed) {
this.proxyed=proxyed;
}
@Override
public Object invoke(Object proxy1, Method method, Object[] args)
throws Throwable {
Object result;
System.out.println("start to invoke");
//调用原始对象的方法
result=method.invoke(proxyed, args);//返回调用结果
System.out.println("end to invoke");
return result;
}
}
测试
Hello world=new HelloWord();
//实例化一个动态代理类
InvocationHandler in=new HelloHandler(world);
//获取反射类
Class cls=Proxy.getProxyClass(world.getClass().getClassLoader(), world.getClass().getInterfaces());
//获取反射构造
Constructor ct = cls.getConstructor(new Class[]{InvocationHandler.class});
//返回实例
Hello subject =(Hello) ct.newInstance(new Object[]{in});
//调用
subject.say(5,10);
可以合并起来写
Hello subject =(Hello)Proxy.newInstance(world.getClass().getClassLoader(), world.getClass().getInterfaces(),in);
subject.say(5,10);
三.了解jvm体系
Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现。Java虚拟机有自己想象中的硬件,如处理器、堆栈、寄存器等,还具有相应的指令系统。
Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。
Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。
JVM定义了控制Java代码解释执行和具体实现的五种规格,它们是:
*JVM指令系统 : Java指令系统是以Java语言的实现为目的设计的,其中包含了用于调用方法和监视多先程系统的指令。Java的8位操作码的长度使得JVM最多有256种指令,目前已使用了160多种操作码。
*JVM寄存器: 4个最为常用的寄存器。它们是: pc程序计数器、optop操作数栈顶指针、frame当前执行环境指针 vars指向当前执行环境中第一个局部变量的指针。所有寄存器均为32位。pc用于记录程序的执行。optop,frame和vars用于记录指向Java栈区的指针。速度最快。
*JVM栈结构: 局部变量、执行环境、操作数栈。局部变量用于存储一个类的方法中所用到的局部变量。vars寄存器指向该变量表中的第一个局部变量。执行环境用于保存解释器对Java字节码进行解释过程中所需的信息。操作数栈用于存储运算所需操作数及运算的结果。速度仅次于寄存器。存储java引用,不会存储java对象。
*JVM碎片回收堆:Java类的实例所需的存储空间是在堆上分配的。解释器具体承担为类实例分配空间的工作。解释器在为一个实例分配完存储空间后,便开始记录对该实例所占用的内存区域的使用。一旦对象使用完毕,便将其回收到堆中。对内存进行释放和回收的工作是由Java运行系统承担的。
*JVM存储区: JVM有两类存储区:常量缓冲池和方法区。常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java方法的字节码。
注:存储区有如上4种JVM寄存器、JVM栈结构、JVM碎片回收堆、JVM存储区(常量缓冲池和方法区)。还可以有如序列化存储的方式。
注:Java的编译方式有两种,一种是和C++等语言一样的,把源代码编译成和本地机器平台相关的机器语言,叫即时编译。另一种是编译成一种中间的字节码,与机器平台无关的,这种也是常用的,叫解释型的。
javac 编译器,用于将java源代码文件编译成字节码.
java 解释器,用于执行java字节码
JVM的体系结构(如图)
JAVA虚拟机的运行过程(如图)
四.多线程
概念:在一个程序中,这些独立运行的程序片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。
目的:多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。
线程的4个状态:
New(新建)线程对象仅仅是创建,并没有调用start()方法。
runnable(就绪)只要调度程序将时间片分配给线程,它便可以立刻运行
Blocked(阻塞)线程可以运行,但是由于某个条件阻止了它的运行。当线程为阻塞状态时,调度程序将不会将时间片分给它。
Dead(死亡)
注:线程进入阻塞的某些原因:1)sleep()方法,使线程进入休眠状态。2)wait()方法,使线程挂起。3)线程等待某个输入/输出的完成。4)线程试图调用某个对象上的同步控制方法,但是该对象锁不可用。5)suspend(),但是Java 2 中被废弃了。通常有3种可以恢复:sleep自动恢复、对于suspend调用resume恢复、对于wait可以用通知(notify或notiyA11)方法使其恢复。
实现方式:继承Thread类和实现Runnable接口重写run方法
线程的级别:
static int MAX_PRIORITY
线程可以具有的最高优先级。
static int MIN_PRIORITY
线程可以具有的最低优先级。
static int NORM_PRIORITY
分配给线程的默认优先级。
主要方法
void interrupt() //中断线程。
static boolean interrupted() //测试当前线程是否已经中断。
boolean isAlive()//测试线程是否处于活动状态。
boolean isDaemon() //测试该线程是否为守护线程。
boolean isInterrupted() //测试线程是否已经中断。
void join() //等待该线程终止。
run()//如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回
static void sleep(long millis)//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
void start()// 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
void stop()// 过时
suspend()//已过时。
resume()// 已过时。
destroy()//已过时。
yield()//暂停当前正在执行的线程对象,并执行其他线程。
执行图
注意点:
sleep() 方法:sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。可能给其他线程执行的机会(自私,睡着了,不释放锁,时间到了才放锁)
yield() 方法:yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。不过yield()只能使同等级别的线程获取执行的机会(公平竞争,释放大家再次选举)。而sleep(1000)使同级别或不同级别的都有可能。
wait() 和 notify() 和notifyAll()方法是Object中定义的方法:
必须在synchronized代码块中使用,在synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象的锁标志,进入等待的状态,并且可以调用notify()或者notifyAll()方法通知正在等待的其他线程。notify()通知的是等待队列中的第一个线程,notifyAll()通知的是等待队列中的所有数量。
几个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。
wait()和sleep()方法的区别:
wait()在object类里定义;sleep()在Thread类里定义。
wait()方法只能放在同步方法或同步块中,表示资源同步时,线程需要等待。
sleep()方法可放在任何位置,表示当前线程睡眠。
wait()方法会释放对象锁;sleep()不会释放对象锁。
wait()方法要等待唤醒之后,线程才会继续执行。
sleep()则是休眠一段时间,线程自动恢复执行.
sleep()必须捕获异常,而wait(),notify()和notifyAll()不需要捕获异常
notify()和notifyAll()的区别
notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。
notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。
join()是直到执行完(或强制执行一段时间)当前的线程后才往下执行主线程或其他的线程
Java有两种Thread:“守护线程Daemon”与“用户线程User”。
用户线程:Java虚拟机在它所有非守护线程已经离开后自动离开。
守护线程:守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。
setDaemon(boolean on)方法可以方便的设置线程的Daemon模式,true为Daemon模式,false为User模式。setDaemon(boolean on)方法必须在线程启动之前调用,当线程正在运行时调用会产生异常。isDaemon方法将测试该线程是否为守护线程。值得一提的是,当你在一个守护线程中产生了其他线程,那么这些新产生的线程不用设置Daemon属性,都将是守护线程,用户线程同样。
例:我们所熟悉的Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。
在守护线程中创建非守护线程(前提是非守护线程执行语句要放在守护线程前),则不会因为其他所有非守护线程的结束而程序运行结束。总之一句话,如果当前还有一个非守护线程在执行,则程序不会结束(不管他是不是在守护线程中创建)。因为在守护线程中创建的默认都是守护线程,当然如果把守护线程创建的默认守护线程改成非守护线程,运行结果就不一样了。而在非守护线程中创建守护线程就很好理解了。
同步:
同步有两种方法。一种同步方法,一种同步代码!分别是synchronized,wait与notify
注意:
同步和异步有何异同,在什么情况下分别使用他们?。
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
synchronized 和 java.util.concurrent.locks.Lock 的异同
主要相同点: Lock能完成synchronized所实现的所有功能
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。
其他:
关于创建多进程、线程组、线程池查看本博客的例子
重点:理解基本的概念和使用 ,以及经典的“生产者和消费者”