第十二周笔记
九、对象序列化控制输入输出
对象序列化(Serialize) 指将一个Java对象写入IO流中,
对象的反序列化(Deserialize) 则指从IO流中恢复该Java对象。
如果想让某个Java对象能够序列化,则必须让它的类实现java.io.Serializable 接口。
Serializable接口是一个空接口,实现该接口无须实现任何方法,它只是告诉JVM该类可以被序列化机制处理。通常建议程序创建的每个JavaBean类都实现Serializable。
ObjectInput 接口与ObjectOutput 接口分别继承了DataInput 和 DataOutput接口,主要提供用于读写基本数据和对象数据的方法。
ObjectInput 接口提供了readObject()方法,此方法用于将对象从流中读出。ObjectOutput 提供了writeObject()方法,此方法用于将对象写入流中。
ObjectInput与ObjectOutput都是接口,不能创建对象,只能使用分别实现了这两个接口的ObjectInputStream类和ObjectOutputStream类来创建对象。
1、序列化
ObjectOutputStream 类继承了OutputStream 类,同时实现了ObjectOutput接口,提供将对象序列化并写入流中的功能,该类的构造:
public ObjectOutputStream(OutputStream out)
该构造方法需要传入一个OutputStream
对象,用来表示将对象二进制流写入到指定的OutputStream
中
1) 创建一个ObjectOutputStream
对象
//创建 ObjectOutputStream 输出流 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("test.txt"));
2)调用ObjectOutputStream对象的writeObject()方法输出可序列化对象。
//将一个Person对象输出到输出流中 oos.writerObject(per);
2、反序列化
ObjectInputStream类继承了InputStream类,同时实现了ObjectInput接口,提供了将对象序列化并从流中读取出来的功能。该类方法的构造方法如下:
public ObjectInputStream(InputStream in)
该构造方法需要传入一个InputStream对象,用来创建从指定InputStream读取的ObjectInputStream
1)创建一个ObjectInputStream输入流,这个输入流是一个处理流,所以必须建立在其他节点流的基础之上。
//创建一个ObjectInputStream输入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("object.txt"))
2)调用ObjectInputStream对象的readObject()方法读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该Java对象的类型,则可以将该对象强制类型转换成其真实的类型。
3、Java序列化编号
序列化编号有两种显示生成方式:
1、默认的1L,比如: private static final long serialVersionUID = 1L
2、根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段。
多线程
一、多线程基础
1.进程:在计算机中,把一个任务称为一个进程,某些进程内部还需要同时执行对个子任务。
操作系统调度的最小任务单位是线程。
进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。
多进程模式(每个进度只有一个线程)
多线程模式(一个进程有多个线程)
多进程+多线程模式(复杂度最高)
PHP是一种单线程模式。
线程创建第一种方式:通过继承Thread来创建线程,并重写run方法。
线程创建第二种方式:实现Runnable接口创建线程,并重写run方法
2、进程 vs 线程
进程和线程是包含关系,和多线程相比,多进程的缺点在于:
1、创建进程比创建线程开销大,尤其是在Windows系统上;
2、进程间通信比线程间通信要慢,因为线程间通信就是读写同一个变量,速度很快。
而多进程的优点在于:
多进程稳定性高,因为在多进程的情况下,一个进程崩溃不会影响其他进程,而在多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。
3.多线程
和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。多线程编程的复杂度高,调试更困难。
Java多线程编程的特点又在于:
-
多线程模型是Java程序最基本的并发模型;
-
网络、数据库、Web开发等都依赖Java多线程模型。
线程创建第一种方式:通过继承Thread来创建线程,并重写run方法。
线程创建第二种方式:实现Runnable接口创建线程,并重写run方法
三、线程的状态
JDK中用Thread.State类定义了线程的几种状态 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU并临时中止自己的执行,进入阻塞状态 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
Java线程的状态有以下几种:
-
New:新创建的线程,尚未执行;
-
Runnable:运行中的线程,正在执行
run()
方法的Java代码; -
Blocked:运行中的线程,因为某些操作被阻塞而挂起;
-
Waiting:运行中的线程,因为某些操作在等待中;
-
Timed Waiting:运行中的线程,因为执行
sleep()
方法正在计时等待; -
Terminated:线程已终止,因为
run()
方法执行完毕。线程终止的原因有:
-
线程正常终止:
run()
方法执行到return
语句返回; -
线程意外终止:
run()
方法因为未捕获的异常导致线程终止; -
对某个线程的
Thread
实例调用stop()
方法强制终止(强烈不推荐使用)。 -
小结
1、Java线程对象
Thread
的状态包括:New
、Runnable
、Blocked
、Waiting
、Timed Waiting
和Terminated
;2、通过对另一个线程对象调用
join()
方法可以等待其执行结束;3、可以指定等待时间,超过等待时间线程仍然没有结束就不再等待;
4、对已经运行结束的线程调用
join()
方法会立刻返回。
-
-
四、中断线程:interrupt
守护线程: * 守护线程又称为后台线程,默认创建的线程都是普通线程,或者称为前台线程, * 线程提供了一个方法: * void setDaemon(boolean on) * 只有调用该方法并且传入参数为true时,该线程才会被设置为守护线程。 * 守护线程在使用上与普通线程没有差别,但是在结束时会有一个差别, * 即:线程结束时,所有正在运行的守护线程都会被强制停止 * 进程结束:当一个进程中所有的普通线程都结束时,进程才会结束
多线程并发的安全问题: * 产生:当多个线程并发操作时,由于线程切换实际的不确定性,会导致操作资源的代码 * 顺序为按照设计顺序执行,出现操作混乱的情况,严重时可能导致系统瘫痪。 * 解决:将并发操作同一资源改为同步运行,即:有先后顺序的操作 * * 同步与异步: * 同步:程序运行有先后顺序 * 异步:程序运行没有先后顺序
使用synchronized
解决了多线程同步访问共享变量的正确性问题。但是,它的缺点是带来了性能下降。因为synchronized
代码块无法并发执行。此外,加锁和解锁需要消耗一定的时间,所以,synchronized
会降低程序的执行效率。
不需要synchronized的操作
JVM规范定义了几种原子操作:
-
基本类型(
long
和double
除外)赋值,例如:int n = m
; -
引用类型赋值,例如:
List<String> list = anotherList
。
long
和double
是64位数据,JVM没有明确规定64位赋值操作是不是一个原子操作,不过在x64平台的JVM是把long
和double
的赋值作为原子操作实现的。
概括一下如何使用synchronized
:
-
找出修改共享变量的线程代码块;
-
选择一个共享实例作为锁;
-
使用
synchronized(lockObject) { ... }
。
在使用synchronized
的时候,不必担心抛出异常。因为无论是否有异常,都会在synchronized
结束处正确释放锁:
小结
1、多线程同时读写共享变量时,会造成逻辑错误,因此需要通过synchronized
同步;
2、同步的本质就是给指定对象加锁,加锁后才能继续执行后续代码;
3、注意加锁对象必须是同一个实例;
4、对JVM定义的单个原子操作不需要同步。
小结
1、用synchronized
修饰方法可以把整个方法变为同步代码块,synchronized
方法加锁对象是this
;
2、通过合理的设计和数据封装可以让一个类变为“线程安全”;
3、一个类没有特殊说明,默认不是thread-safe;
4、多线程能否安全访问某个非线程安全的实例,需要具体问题具体分析。
-
死锁发生后,没有任何机制能解除死锁,只能强制结束JVM进程。
因此,在编写多线程应用时,要特别注意防止死锁。因为死锁一旦形成,就只能强制结束进程。
那么我们应该如何避免死锁呢?答案是:线程获取锁的顺序要一致。即严格按照先获取
lockA
,再获取lockB
的顺序,改写dec()
方法如下:
public void dec(int m) { synchronized(lockA) { // 获得lockA的锁 this.value -= m; synchronized(lockB) { // 获得lockB的锁 this.another -= m; } // 释放lockB的锁 } // 释放lockA的锁 }
十、wait和notify
在Java程序中,synchronized
解决了多线程竞争的问题。例如,对于一个任务管理器,多个线程同时往队列中添加任务,可以用synchronized
加锁:
小结
wait
和notify
用于多线程协调运行:
-
在
synchronized
内部可以调用wait()
使线程进入等待状态; -
必须在已获得的锁对象上调用
wait()
方法; -
在
synchronized
内部可以调用notify()
或notifyAll()
唤醒其他等待线程; -
必须在已获得的锁对象上调用
notify()
或notifyAll()
方法; -
已唤醒的线程还需要重新获得锁后才能继续执行。
十一、ReentrantLock
小结
1、ReentrantLock
可以替代synchronized
进行同步;
2、ReentrantLock
获取锁更安全;
3、必须先获取到锁,再进入try {...}
代码块,最后使用finally
保证释放锁;
4、可以使用tryLock()
尝试获取锁。
十二、condition
Condition
提供的await()
、signal()
、signalAll()
原理和synchronized
锁对象的wait()
、notify()
、notifyAll()
是一致的,并且其行为也是一样的:
-
await()
会释放当前锁,进入等待状态; -
signal()
会唤醒某个等待线程; -
signalAll()
会唤醒所有等待线程; -
唤醒线程从
await()
返回后需要重新获得锁。
小结
1、Condition
可以替代wait
和notify
;
2、Condition
对象必须从Lock
对象获取。
十三、ReadwriteLock
使用ReadWriteLock
可以解决这个问题,它保证:
-
只允许一个线程写入(其他线程既不能写入也不能读取);
-
没有写入时,多个线程允许同时读(提高性能)。
使用ReadWriteLock
可以提高读取效率:
-
ReadWriteLock
只允许一个线程写入; -
ReadWriteLock
允许多个线程在没有写入时同时读取; -
ReadWriteLock
适合读多写少的场景。
十四、StampedLock
1、StampedLock
提供了乐观读锁,可取代ReadWriteLock
以进一步提升并发性能;
2、StampedLock
是不可重入锁。
十五、线程池作用:重复利用资源
将线程池停止,但是线程池会将当前的任务执行完之后在停止 pool.shutdown();
该方法是将线程立刻停止,不会等待线程池完成所有任务 threadPool.shutdownNow();
newCachedThreadPool: * 创建一个可缓存的无界线程池,如果线程池长度超过处理需要,可灵活回收空线程,若无可回收,则新建线程。 * 当线程池中的线程空闲时间超过60s,则会自动回收该线程,当任务超过线程池的线程数则创建新的线程, * 线程池的大小上限为Integer.MAX_VALUE,可看作无限大。
newScheduledThreadPool: 创建一个定长的线程池,可以指定线程池核心线程数,支持定时及周期性任务的执行
newSingleThreadExecutor: * 创建一个单线程化的线程池,它只有一个线程,用仅有的一个线程来执行任务,