1.序列化
序列化版本号:serialVersionUID,是一个类的序列化版本号。如果在反序列化时,类的 serialVersionUID 与序列化时的版本号不匹配,那么会抛出 InvalidClassException
异常,表示类的版本不兼容,无法进行反序列化。
如果流量没有定义,JDK会自动给与一个版本号,当该类发生变化(属性和方法),该序列化版本号会发生变化,反序列化失败
如果已经自定义版本号,只要该版本号不发生变化,即使类中属性或者方法改变,该类的对象依旧可以反序列化。
transient关键字,可避免被序列化。属性值不会被序列化出去,其会是默认值。
缓冲流中的flush方法有内容,写出都有flush,写出完都要flush。
package com.easy724;
import java.io.*;
public class EasySerVersion {
public static void main(String[] args) {
//序列化版本号serialVersionUID 是一个类的序列化版本号
//如果流量没有定义,JDK会自动给与一个版本号,当该类发生变化(属性和方法),该序列化版本号会发生变化,反序列化失败
//如果已经自定义版本号,只要该版本号不发生变化,即使类中属性或者方法改变,该类的对象依旧可以反序列化
Student stu=new Student("张三","男",99);
writeStudent(stu);
Student readStu=readStudent();
System.out.println(readStu);
//反序列化的对象是一个新对象
}
//序列化版本号
public static Student readStudent(){
File file=new File("D:\\student.data");
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();
}
}
}
}
public static void writeStudent(Student stu){
FileOutputStream fos=null;
ObjectOutputStream oos=null;
File file=new File("D:\\student.data");
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();//缓冲流中的flush有内容,写出都有flush,写出完都要flush
}catch (IOException e){
e.printStackTrace();
}finally {
if (oos!=null){
try {
oos.close();
}catch (IOException e){
e.printStackTrace();
}
}
if (fos!=null){
try {
oos.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
2.线程
线程(Thread):程序运行阶段不同的运行路线
自定义线程 继承 Thread
常用方法:
1.start():启动线程,当多线程同时启动时,先运行的线程先启动,但是各线程会交叉运行。
2.run():要在继承线程(Thread)的类中重写run()方法,定义线程要执行的任务,当main方法中连续出现run()方法,就是按顺序执行,不存在交叉,是一个线程。与重写的不一样。
3.sleep():休眠方法。sleep是Thread类的静态方法,让运行到此处的代码线程休眠一定时间(以毫秒为单位)。休眠后会自动启动线程。
4.Thread.currentThread():获取当前线程对象。
5.yeild():礼让。让出cpu资源,让cpu重新分配。防止一条线程长时间占用cpu资源,达到cpu资源合理分配的效果。【sleep(0)也可以达到该效果,但是表示的阶段不同】
6.join():加入(插队)。比如在A线程中执行了B.join(),B线程运行完毕后,A线程再运行。
7.priority():设置优先级。优先级越高,获得cpu资源的几率越大。优先级范围1~10,默认5,设置其他值会报错。
关闭方法:
1.执行stop方法(不推荐)。
2.调用interrupt()方法,设置中断状态,这个先不回中断,我们需要在线程内部判断,中断状态是否被设置,然后执行中断操作。isInterrupted()是判断中断状态。
3.自定义一个状态属性,在线程外部设置此属性,然后影响线程内部的运行。
线程状态:
1) 新建
当用new关键字创建一个线程时,还没调用start 就是新建状态。
2) 就绪
调用了 start 方法之后,线程就进入了就绪阶段。此时,线程不会立即执行run方法,需要等待获取CPU资源。
3) 运行
当线程获得CPU时间片后,就会进入运行状态,开始执行run方法。
4) 阻塞
当遇到以下几种情况,线程会从运行状态进入到阻塞状态。
-
调用sleep方法,使线程睡眠。
-
调用wait方法,使线程进入等待。
-
当线程去获取同步锁的时候,锁正在被其他线程持有。
需要注意的是,阻塞状态只能进入就绪状态,不能直接进入运行状态。因为,从就绪状态到运行状态的切换是不受线程自己控制的,而是由线程调度器所决定。只有当线程获得了CPU时间片之后,才会进入运行状态。(也就是等待阶段)
5) 终止
当run方法正常执行结束时,或者由于某种原因抛出异常都会使线程进入终止状态。另外,直接调用stop方法也会停止线程。但是,此方法已经被弃用,不推荐使用。
注意:当使用yield方法或者失去cpu资源时,将从运行阶段返回到就绪阶段,而使用sleep方法则是进入等待阶段。
volatile关键字的作用
1.可见性:
当一个线程修改了volatile修饰的变量的值,其他线程可以立即看到这个修改,保证了共享变量的可见性。
2.禁止指令重排序:
编译器和处理器在编译和执行代码时,可能会对指令进行重排序,但是volatile关键字可以禁止这种重排序,保证了程序的正确性。
3.保证原子性:
volatile关键字可以保证一些简单的操作的原子性,例如++操作,但是对于复合操作,volatile关键字无法保证原子性。
二、volatile关键字和synchronized关键字的区别
1.volatile关键字保证了共享变量的可见性和禁止指令重排序,但是无法保证原子性,synchronized关键字可以保证原子性、有序性和可见性。
2.volatile关键字适用于一些简单的操作,例如++操作,而synchronized关键字适用于复合操作。
3.volatile关键字不会造成线程阻塞,而synchronized关键字可能会造成线程阻塞。
代码:
package com.easy724.thread;
public class EasyThreadA {
//线程
//程序运行阶段不同的运行路线
//Thread
//自定义线程 继承 Thread
public static void main(String[] args) {
//实例化线程对象
Thread a=new ThreadA();
Thread b=new ThreadA();
//开启线程
a.start();//启动线程,先a线程后b线程,两个线程可能会交叉运行
b.start();
a.run();//main线程,跟b.run()是按顺序进行,不存在交叉,是一个线程
b.run();
}
}
class ThreadA extends Thread{
//重写run方法,定义线程要执行的任务
@Override
public void run(){
for (int i = 0; i <21 ; i++) {
System.out.print(i+this.getName()+" ");
}
}
}
package com.easy724.thread;
public class EasyThreadB {
//线程常用的方法
//sleep 休眠方法
public static void threadSleep() throws InterruptedException {
//sleep是一个Thread类的静态方法
System.out.println("1--------");
//让运行到该行代码的线程休眠5秒
//休眠后会自动启动线程
Thread.sleep(1000*5);
System.out.println("2--------");
}
public static void threadBSleep(){
Thread t=new ThreadB();
t.start();
}
//获取当前线程对象
//Thread.currentThread()
public static void current(){
System.out.println(Thread.currentThread().getName());
}
//设置优先级
public static void priority(){
Thread a=new ThreadB();
Thread b=new ThreadB();
//设置优先级
a.setPriority(4);
b.setPriority(6);
//优先级越高,获取CPU资源的几率越大
//优先级 1~10,默认5
//设置其他值报错
a.start();
b.start();
}
//礼让 yeild
//作用:让出cpu资源,让cpu重新分配
//防止一条线程长时间占用cpu资源,达到cpu资源合理分配的效果
//sleep(0)也可以达到该效果
public static void threadYeild(){
Thread a=new ThreadC();
Thread b=new ThreadC();
a.start();
b.start();
}
//join() 成员方法 加入(插队)
//在A线程中执行了B.join() B线程运行完毕后,A线程再运行
public static void threadJoin(){
Thread a=new ThreadD();
Thread b=new ThreadD(a);
a.start();
b.start();
}
public static void main(String[] args) throws Exception {
//threadSleep();
//threadBSleep();
//current();
//priority();
//threadYeild();
threadJoin();
}
}
class ThreadB extends Thread{
@Override
public void run() {
for (int i = 0; i <21 ; i++) {
if (i%8==0){
try {
Thread.sleep(1000*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i+this.getName());
}
}
}
class ThreadC extends Thread{
@Override
public void run() {
for (int i = 0; i <21 ; i++) {
if (i%3==0){
Thread.yield();
}
System.out.println(i+this.getName());
}
}
}
class ThreadD extends Thread{
public ThreadD(Thread t){
this.t=t;
}
public ThreadD(){
}
private Thread t;
@Override
public void run() {
for (int i = 0; i <2000; i++) {
if (i==10&&t!=null&&t.isAlive()){
System.out.println(this.getName()+"执行Join");
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i+this.getName());
}
}
}
package com.easy724.thread;
public class EasyThreadC {
public static void threadstop() {
Thread a=new ThreadE();
a.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
a.stop();
}
public static void threadInterrupted(){
Thread a=new ThreadF();
a.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
a.interrupt();
}
public static void stopThread(){
Thread a=new ThreadG();
a.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
((ThreadG) a).stop=true;
}
public static void main(String[] args) {
//threadstop();
//threadInterrupted();
stopThread();
}
}
//关闭线程
//1.执行stop方法(不推荐)
//2.调用interrupt()方法,设置中断状态,这个先不回中断,我们需要在线程内部判断,中断状态是否被设置,然后执行中断操作
//3.自定义一个状态属性,在线程外部设置此属性,然后影响线程内部的运行
class ThreadE extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
class ThreadF extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if (Thread.currentThread().isInterrupted()){
break;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
class ThreadG extends Thread{
volatile boolean stop=false;
@Override
public void run() {
while (!stop){
//System.out.println("A");
}
}
}
3.线程安全
线程安全:多个线程操作一个对象,不会出现结果错乱的情况(数据缺失)。
线程不安全:StringBuilder就是线程不安全。
要做到线程安全,我们可以使用synchronized对方法或者代码块加锁,达到线程同步的效果。
线程同步:一个线程一个线程的执行,不能同时执行。
使用synchronized关键字修饰的代码块,同一时间内,只允许一个线程执行此代码。
使用Synchronizated需要指定锁对象。
synchronized修饰方法 1.成员方法 用this 2.静态方法 用类的类对象 obj.getClass()
锁的分类:
根据有无锁对象:悲观锁(有锁对象)和乐观锁(无锁对象)。
synchronized是悲观锁,乐观锁的实现方式:CAS和版本号控制。
根据公平度:公平锁和非公平锁。公平锁就是先来后到,非公平锁。
可重入锁:在同步代码块中遇到相同的锁对象的同步代码块,不需要再获取锁对象的权限,直接进入执行。
根据线程状态不同:偏向锁,轻量级锁(自旋锁),重量级锁。
synchronized是什么锁,这是相对的。
CAS和版本号控制:
悲观锁,认为所有线程都会对数据进行修改,所以某个线程拿数据的时候,就会上锁阻塞其余所有线程,直到锁的释放,然后其余线程继续竞争这个锁,往复循环。
乐观锁,总是认为最好的情况,每次去拿数据都认为别人不会修改,所以不会上锁,但是在更新的时候会判断在此期间别人有没有去更新这个数据,实现技术就是CAS算法和版本号机制。
CAS操作分为三部分:
1、读取内存值。
2、比较内存值与期望值是否相等。
3、如果相等,新值写入内存,否则此次操作无效。
注意(ABA问题):CAS只能对比值是否相同,但不能确定这个值是否中间发生过改变。如果对比值相等,但值之后发生改变会出现问题。
版本号控制:
为了解决CAS的ABA问题,引出了版本号机制,在数据表中加上一个version版本号的字段,表示数据被修改的次数,这个值只能增加,不能减小,当某个线程要更新数据值时,在读取数据的同时也会读取version值,若读取的version值为当前数据库中的version值相等时,才会更新数据值,否则认为这个数据已被修改,则不做任何操作。
代码:
package com.easy724.thread;
public class SyncThreadA {
//线程安全:
//多个线程操作一个对象,不会出现结果错乱的情况(数据缺失)
//线程不安全:
//StringBuilder就是线程不安全
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) {
e.printStackTrace();
}
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");
}
}
}
package com.easy724.thread;
public class SyncThreadB {
//要做到线程安全,我们可以使用synchronized对方法或者代码块加锁,达到线程同步的效果
//线程同步:一个线程一个线程的执行,不能同时执行
//使用synchronized关键字修饰的代码块,同一时间内,只允许一个线程执行此代码
//修饰方法
public static synchronized void test(){
try {
System.out.println("----------进入方法"+Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println("----------执行完毕"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//修饰代码块
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) {
e.printStackTrace();
}
System.out.println("结束同步代码块"+Thread.currentThread().getName());
}
}
//使用Synchronizated需要指定锁对象
//synchronized修饰方法 成员方法 this
// 静态方法 类的类对象 obj.getClass()
//锁的分类:
//根据有无锁对象:悲观锁(有锁对象)和乐观锁(无锁对象)
//synchronized是悲观锁,乐观锁的实现方式:CAS和版本号控制
//根据公平度:公平锁和非公平锁。公平锁就是先来后到,非公平锁
//可重入锁:在同步代码块中遇到相同的锁对象的同步代码块,不需要再获取锁对象的权限,直接进入执行
//根据线程状态不同:偏向锁,轻量级锁(自旋锁),重量级锁。
//synchronized是什么锁,这是相对的。
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{
@Override
public void run() {
SyncThreadB.testA();
}
}
4.BIO,AIO,NIO
java BIO:同步并阻塞
在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。
服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
java NIO:同步非阻塞
在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。
服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
java AIO:异步非阻塞
在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。
服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是有OS先完成了再通知服务器应用去启动线程进行处理。
标签:JAVA,Thread,Day9,版本号,void,线程,new,自学,public From: https://blog.csdn.net/m0_63367052/article/details/140667720