序列化;反序列化
序列化
import java.io.*;
public class EasySerVersion {
public static void writeStudent(Student stu) {
FileOutputStream fos = null;
ObjectOutputStream oos = null;
File file = new File("D:\\student.date");
if (!file.exists()) {
try{
file.createNewFile();
}catch(IOException e){
e.printStackTrace();
}
}
try{
fos=new FileOutputStream(file);
oos=new ObjectOutputStream(fos);
oos.writeObject(stu);
oos.flush();
}catch(IOException e){
e.printStackTrace();
}finally{
if(oos!=null){
try{
oos.close();
}catch(IOException e){
e.printStackTrace();
}
}
if(fos!=null){
try{
fos.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
}
反序列化
反序列化的对象是一个新的对象
import java.io.*;
public class EasySerVersion {
//序列化版本号
public static Student readStudent(){
File file = new File("D:\\student.date");
FileInputStream fis = null;
ObjectInputStream ois = null;
try{
fis=new FileInputStream(file);
ois=new ObjectInputStream(fis);
Object obj=ois.readObject();
if(obj instanceof Student){
return (Student)obj;
}
return null;
}catch(Exception e){
e.printStackTrace();
return null;
}finally{
if(ois!=null){
try{
ois.close();
}catch(IOException e){
e.printStackTrace();
}
}
if(fis!=null){
try{
fis.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
}
Student类
serialVersionUID 是一个类的序列化版本号,如果该量没有定义,JDK会自动给予一个版本号。
当前类发生变化时 ,序列化版本号会发生变化,反序列化就会失败 。
自定义该版本号,只要该版本号不发生变化,即使类中属性/方法改变,该类的对象依旧可以反序列化。
private static final long serialVersionUID=1L;
transient关键字的作用是,在对象序列化过程中,用该关键字修饰的实例变量不会被序列化。换句话说,该实例变量在对象的序列化的过程中会丢失,即序列化后的对象无法获取该实例变量的值。
private transient String sex;
//禁止属性的值被序列化
import java.io.Serial;
import java.io.Serializable;
public class Student implements Serializable {
@Serial
private static final long serialVersionUID=1L;
private transient Teacher tea=new Teacher();
//private String name;
private transient String sex;
//禁止属性的值被序列化
private double score;
private String code;
public void test(){}
@Override
public String toString() {
return "Student{" +
//"name='" + name + '\'' +
", sex='" + sex + '\'' +
", score=" + score +
", code=" + code +
'}';
}
public Student() {
}
public Student(String name, String sex, double score) {
//this.name = name;
this.sex = sex;
this.score = score;
}
//public String getName() {
// return name;
//}
//
//public void setName(String name) {
// this.name = name;
//}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
线程
程序运行阶段的不同运行路线
线程的生命周期:
线程类 Thread
自定义线程继承Thread类
class ThreadA extends Thread{
//重写run方法,定义线程要执行的任务
@Override
public void run(){
for(int i=0;i<20;i++){
System.out.println(i+this.getName());
}
}
}
实例化线程对象
Thread a=new ThreadA();
常用方法
开启线程
a.start();
休眠
Thread.sleep(5000);//5000毫秒
sleep是一个Thread类的静态方法,让运行到该行代码的线程休眠5秒,休眠后会自动启动线程
获取当前线程对象
Thread.currentThread()
设置优先级
a.setPriority(4);
优先级越高,获取CPU资源的几率越大。优先级是从小到大是1—10,默认为5,设置其他值会报错
礼让
Thread.yield();
让出CPU资源,让CPU重新分配,防止一条线程长时间占用CPU资源,达到CPU资源合理分配的效果(.sleep(0)也可以达到上面效果)
加入(插队)
B.join();
B线程运行完毕后,A线程再运行。join方法与yield方法不同,join会完全让出CPU资源。
关闭线程
1.执行 .stop() 方法(方法已被删除,不推荐)
a.stop();
2.调用 interrupt() 设置中断状态,这个线程不会中断,我们需要在线程内部判断中断状态是否被设置,然后执行中断操作。
a.interrupt();
class ThreadF extends Thread{
@Override
public void run() {
for(int i=0;i<10000;i++){
if (Thread.currentThread().isInterrupted()){
break;
}
System.out.println(i);
}
}
}
3.自定义一个状态属性,在线程外部设置此属性,影响线程内部的运行
class ThreadG extends Thread{
volatile boolean stop=false;
@Override
public void run() {
while(!stop){
System.out.println("A");
}
}
}
线程安全
线程安全
多个线程操作一个对象,不会出现结果错乱的情况(数据丢失,缺失);StringBuffer就是线程安全的。
public class EasySyncThreadA {
public static void main(String[] args) {
StringBuffer strB=new StringBuffer();
//线程可以执行的任务
RunA r=new RunA(strB);
Thread a=new Thread(r);
a.start();
Thread b=new Thread(r);
b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(strB.length());
}
}
//实现Runnable接口是实现线程的一种方式,它并不是线程类
class RunA implements Runnable {
StringBuffer strB;
public RunA(StringBuffer strB) {
this.strB = strB;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
strB.append("0");
}
}
}
线程不安全
多个线程操作一个对象,会出现结果错乱的情况(数据丢失,缺失);StringBuilder就是线程不安全的。
public class EasySyncThreadA {
public static void main(String[] args) {
StringBuilder strB=new StringBuilder();
//线程可以执行的任务
RunA r=new RunA(strB);
Thread a=new Thread(r);
a.start();
Thread b=new Thread(r);
b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(strB.length());
}
}
class RunA implements Runnable {
StringBuilder strB;
public RunA(StringBuilder strB) {
this.strB = strB;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
strB.append("0");
}
}
}
synchronized关键字
要做到线程安全,我们可以使用synchronized关键字对方法或者代码块加锁,达到线程同步的效果。 使用synchronized关键字修饰的方法或代码块,同一时间内,只能允许一个线程执行此代码。
public class SyncThreadB {
public static synchronized void test() {
try {
System.out.println("----进入方法"+Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println("----执行完毕"+Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void testA(){
System.out.println("进入方法"+Thread.currentThread().getName());
synchronized(SyncThreadB.class){//锁对象
System.out.println("进入同步代码块"+Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("结束同步代码块"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Runnable r = new RunB();
Thread a = new Thread(r);
Thread b = new Thread(r);
a.start();
b.start();
}
}
class RunB implements Runnable {
public void run() {
SyncThreadB.testA();
}
}
运行结果
使用synchronized需要指定锁对象
锁类
synchronized(SyncThreadB.class)
锁方法
public synchronized static void method() {
}
锁的分类
根据有无锁对象分为悲观锁和乐观锁
悲观锁有锁对象 ,乐观锁没有锁对象;synchronized 是悲观锁
乐观锁实现方式CAS和版本号控制
使用CAS
(Compare-And-Swap)操作实现乐观锁。以下是一个简单的乐观锁实现示例:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class OptimisticLockExample {
// 使用AtomicInteger实现乐观锁
private AtomicInteger value = new AtomicInteger(0);
// 使用AtomicStampedReference实现带有时间戳的乐观锁
private AtomicStampedReference<Integer> valueWithStamp = new AtomicStampedReference<>(0, 0);
// 获取值的方法
public int getValue() {
return value.get();
}
// 设置值的方法
public void setValue(int newValue) {
while (true) {
int currentValue = value.get();
if (value.compareAndSet(currentValue, newValue)) {
// CAS成功,退出循环
break;
}
// CAS失败,继续尝试
}
}
// 带有时间戳的乐观锁设置值的方法
public void setValueWithStamp(int newValue) {
int[] stamp = new int[1];
int[] value = new int[1];
while (true) {
// 获取当前值和时间戳
value[0] = this.valueWithStamp.get(stamp);
int currentStamp = stamp[0];
// 尝试更新值
if (this.valueWithStamp.compareAndSet(value[0], newValue, currentStamp, currentStamp + 1)) {
// CAS成功,退出循环
break;
}
// CAS失败,继续尝试
}
}
public static void main(String[] args) {
OptimisticLockExample example = new OptimisticLockExample();
example.setValue(10);
System.out.println("Value: " + example.getValue());
example.setValueWithStamp(20);
System.out.println("Value with timestamp: " + example.getValue());
}
}
这个示例中,AtomicInteger
用于实现一个简单的乐观锁,而AtomicStampedReference
用于实现带有时间戳的乐观锁,以防止ABA问题。在设置值的过程中,如果CAS操作失败,线程将继续尝试,直到操作成功为止。
在对象中使用版本号实现乐观锁:
public class OptimisticLockExample {
private int id;
private String data;
private int version; // 版本号
// 构造函数、getter 和 setter 省略
// 模拟更新数据的方法,乐观锁实现
public boolean updateData(int id, String newData, int expectedVersion) {
if (this.id == id && this.version == expectedVersion) {
this.data = newData;
this.version++; // 更新版本号
// 这里可以执行数据库更新操作,示例中省略
return true;
}
return false;
}
// 示例用法
public static void main(String[] args) {
OptimisticLockExample record = new OptimisticLockExample();
record.id = 1;
record.data = "initial data";
record.version = 1;
// 尝试更新数据,期望版本号为1
boolean updated = record.updateData(1, "updated data", 1);
if (updated) {
System.out.println("Record updated successfully");
} else {
System.out.println("Optimistic lock failure. " +
"Another transaction must have changed the data.");
}
}
}
在这个例子中,updateData
方法检查传入的版本号是否与对象的当前版本号匹配。如果匹配,更新数据,并将版本号递增。如果不匹配,方法返回 false
,表示更新失败,因为有其他并发事务已经修改了数据。这个简单的例子展示了乐观锁的基本思想,但在实际的数据库环境中,乐观锁的实现通常会涉及数据库的特定机制,如使用 version
字段和 UPDATE
语句的条件检查
根据公平度不一样分为公平锁和非公平锁
公平锁就是先来后到 java里都是非公平锁
可重入锁 :在同步代码块中遇到相同的锁对象同步代码块,不需要再获取锁对象的权限,直接进入执行;java全都是可重入锁
根据线程状态不同分为偏向锁,轻量级锁(自旋锁),重量级锁;synchronized是偏向锁
Java中volatile关键字
volatile 是 Java 中的一个关键字,它的主要作用是用来确保变量的可见性和阻止指令重排序。
可见性:当一个共享变量被 volatile 修饰后,它会保证修改的值会立即被更新到主内存中,且每次使用该变量时,都会从主内存中重新读取最新的值。这就解决了多线程环境下,一个线程修改了变量,而其他线程不知道的问题。
指令重排序:Java 中的 volatile 还可以防止 JVM 的指令重排序优化。这意味着在 volatile 变量之前的所有操作都会在其之前执行,在 volatile 变量之后的所有操作都会在其之后执行。
下面是一个简单的使用 volatile 的例子:
public class VolatileTest {
volatile boolean flag = false;
public void changeFlag() {
flag = true;
}
public void checkFlag() {
while (!flag) {
// do nothing
}
// 处理其他逻辑
}
}
在这个例子中,flag 变量被 volatile 关键字修饰,确保了当 flag 被修改后,其他线程能立即看到最新的值。checkFlag 方法会一直循环直到 flag 为 true。