Java学习记录(Day9)
- 序列化版本号
首先定义一个Student类继承可序列化,在里面定义属性和toString方法。然后通过字节输出流和对象输出流将Student类对象写入系统文件,也就是序列化的过程。然后通过字节输入流和对象输入流将系统文件中序列化的字节流转换回Student类对象,也就是反序列化的过程。
反序列化回来的对象是一个新的对象
当Student类中的方法和属性发生变化时,反序列化之前存入的字节流就会报错。因为serialVersionUID 是一个类的序列号版本号,如果该量没有定义,jdk就会自动给予一个版本号,当该类发生变化时,该序列化版本号会发生变化,反序列化就会失败。如果自定义版本号,只要该版本号不发生变化,即使该类中属性或者方法发生变化,该类的对象依旧可以反序列化。
设置版本号:
序列化对象,对象的属性也必须是可序列化的
flush()方法的作用:
输出流(如 OutputStream 或 Writer)的 flush() 方法主要用于强制将缓冲区中的数据写入到目标设备或存储介质中。所有输出流都要有flush方法,但是只有缓冲流起作用
关键字
transient 禁止属性的值被序列化
当Student类中的属性被设为transient,其对应的数据不会被读入系统文件中,所以在反序列化的时候,其对应的值不会被返回。
- 线程
1.
(1)定义;程序运行阶段不同的运行路线,自定义线程要继承Thread类
- 实例化线程对象:
首先自定义一个ThreadA类,然后在里面重写run方法。
- 开始线程start()
同时在运行
就是普通对象调用方法,就是普通对象调用方法
2.线程常用的方法
- 休眠的方法 sleep
Thread.sleep(5000);休眠5秒
sleep 是一个Thread的静态方法,休眠后会自动启动该线程
示例:
首先定义一个ThreadB类继承Thread类,重写run方法,打印0到20的数字,当打印到8的倍数时休眠2秒,然后继续往后打印。
然后定义一个静态方法来验证,声明一个ThreadB类的对象t,然后开始t线程。在主函数中,运行该方法。
- 获取当前线程对象Thread.currentThread()
- 设置优先级setPriority();
设置优先级,优先级越高,获取cpu资源的机率越大,优先级从1到10 默认5 设置其他值报错。
首先将a,b都设为ThreadB类的对象,当a,b线程开始时就会执行ThreadB中的打印0到20,遇到8的倍数就会休眠2秒。结果如下所示:
当线程b该执行打印8的倍数时就会进入休眠,此时线程a就会开始打印,同理,当线程a执行到8的倍数时进入休眠,然后线程b开始打印。
- 礼让 Thread.yield();
作用:让出cpu资源让cpu重新分配
防止一条线程永远(长时间)占用cpu资源,达到cpu资源合理分配的效果,相当于sleep(0),但这只是让cpu重新分配资源,并不是将资源让给其他线程。
定义一个ThreadC类继承Thread类,重写run方法,打印0到20的数字,当执行到3的倍数时,执行Thread.yield方法进入休眠,让cpu重新分配资源,分配到资源的线程开始执行。验证时,定义两个ThreadC类的线程,开启两个线程来观察结果。
- join()
加入(插队)
测试方法中,将线程a插入线程b,当线程b执行到特定条件后,就会暂停运行,直到线程a中全部执行完后,才开始继续运行。
下面是ThreadD类的声明以及重写的run方法。
下面是运行结果:具有多样性,但是当线程b遇到10后,就开始执行join方法,等线程a全部运行完,线程b才开始继续运行。
3.关闭线程
(1)执行stop方法(不推荐)
测试方法中,线程a开始后,然后在2秒后强行停止它。
ThreadE类如下声明:
每隔0.5秒打印一个数字,因为2秒后强行关闭线程,所以只能打印3个数字。
因为主函数调用threadStop()方法,所以threadStop()方法中的休眠是主函数休眠而不影响线程a的运行,直到休眠2秒后,线程a执行stop方法,线程a中断。
- 设置中断状态
调用interrupt()方法,这个线程不会中断,我们需要在线程内部判断,中断状态是否被设置,然后执行中断操作
测试方法中,线程a开启后,等待1秒后,执行interrupt()方法将线程a的状态设置为中断状态,然后在线程a中的run方法检测到当前状态为中断状态,所以就跳出了循环,线程a执行完毕。
- 自定义一个状态属性
在线程外部设置此属性,影响线程内部的运行
测试方法中,线程a开始后,进入死循环,打印”A”,主线程休眠1秒后,将线程a的stop变量的值设为true,此时whlie循环的条件不成立,所以跳出循环,线程a终止。
下面是测试方法和ThreadG类的声明及重写的run方法。
Volatile关键字:
volatile 关键字用于修饰变量,当一个变量被声明为 volatile,任何对该变量的写操作都将立即反映到所有线程中,也就是说,当一个线程修改了一个 volatile 变量的值,其它线程可以立即看到这个变化。这是因为 volatile 变量的读写操作不会被缓存在寄存器或处理器高速缓存中,而是直接从主内存读取或写入主内存。
- 线程的生命周期
- 线程同步
- 线程安全
线程安全:多个线程操作一个对象,不会出现结果错乱的情况(缺失)
示例:
声明一个RunA类实现Runnable接口,在里面声明了构造方法和重写了run方法。在主函数中,定义一个StringBuffer类对象strB,创建一个 RunA 类的实例 r,并将 StringBuffer 对象 strB 作为参数传递给它。RunA 类应该是实现了 Runnable 接口的类,它包含了线程可以执行的具体任务。创建一个线程 a,并将 RunA 实例 r 作为线程的目标对象。这意味着线程 a 将会调用 r.run() 方法。然后创建一个线程 b,并将 RunA 实例 r 作为线程的目标对象。线程a和线程b一起进行,StringBuffer.append() 方法是线程安全的,所以这两个线程可以同时访问和修改 strB 而不会出现问题。线程 a 和 b 每次循环都会添加一个字符 '0' 到 strB,并且它们都在各自的循环中独立进行。因此,最终 strB 的长度将是两个线程各自添加的字符数量之和。
StringBuffer 安全的原因是因为它里面的方法全是Synchronized修饰的。
- Synchronized
对方法或者代码块加锁,达到线程同步的效果
使用Synchronized关键字修饰的方法或者是代码块,同一时间内,只能允许一个线程执行此代码
(1)Synchronized方法
声明一个同步的测试方法test,只有第一个进入test方法的线程执行完全部的内容,其他的线程才可以进入。
下面是测试方法和涉及类的声明:
(3)Synchronized代码块
声明一个同步的测试方法testA,多个线程都可以同时进入testA方法,但是只有第一个进入Synchronized代码块的线程执行完全部的内容,其他的线程才可以进入Synchronized代码块。
。
其中SyncThreadB.class是控制哪个线程先进。
- 锁对象
- 定义: 使用synchronized需要指定锁对象
- synchronized修饰方法: 成员方法 this, 静态方法,类的类对象 obj.getClass() Easy.class
- 锁的分类:
A.根据有无锁对象,悲观锁和乐观锁 悲观锁有锁对象,乐观锁没有,synchronized是悲观锁 乐观锁实现方式 CAS和版本号控制
B.公平锁和非公平锁 公平锁就是先来后到 非公平锁
C.可重入锁 在同步代码块中遇到相同的锁对象的同步代码块,不需要再获取对象的权限,直接进入执行
D.根据线程状态不同 偏向锁,轻量级锁(自旋锁),重量级锁