1.serialVersionUID
serialVersionUID是一个类的序列化版本号
先写一个方法,用来向文件输出一个对象。
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("D:\\student.data");
oos=new ObjectOutputStream(fos);
oos.writeObject(stu);
}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();
}
}
}
}
file.exists()用来判断文件是否存在,若不存在就使用file.createNewFile()方法创建一个文件。
写一个方法,从文件输入(读入)对象:
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;//如果对象obj是Student类型,就输出该对象
}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();
}
}
}
}
在main函数中:
因为对文件序列化,再进行反序列化时,会创建出不同的对象
public static void main(String[] args) {
Student stu =new Student("张三","男",99);
writeStudent(stu);//对对象进行序列化,将stu对象存入文件中,
Student stua =readStudent();
System.out.println(stua);
//对文件进行反序列化,使对象能够返回到IDEA控制台上
//反序列化回来的对象是新的对象,所以要创建新的对象stua,再输出stua
}
写一个类Student:
想要此类的对象能够进行序列化,这个类就要实现Serializable接口
public class Student implements Serializable {
private static final long serialVersionUID=1L;
private String name;
private transient String sex;
//transient 暂时的暂存的,禁止属性的值序列化
private double score;
public Student(String name, String sex, double score) {
this.name = name;
this.sex = sex;
this.score = score;
}//带三个参数的构造方法
public Student(){
}//空的构造方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", score=" + score +
'}';
}//重写toString()方法
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;
}
}
2.serialVersionUID序列号是long类型存储的
private static final long serialVersionUID=1L;
注意!!!:
如果该量(版本号)没有自定义,jdk会自动给予一个版本号,当该类发生变化时,序列化版本号会发生变化,反序列化就会失败
如果已经自定义该版本号,只要该版本号不发生变化,即使该类的属性/方法改变,该类的对象依旧可以反序列化
3.关键字transient
transient的意思是暂时的暂存的
transient的作用:禁止属性的值进行序列化
4.线程
线程是程序运行阶段不同的运行路线 线程类为Thread
创建线程类的子类ThreadA:
在类中重写run()方法,定义线程要执行的任务
class ThreadA extends Thread {
//重写run方法,定义线程要执行的任务
@Override
public void run(){
for (int i=0;i<=5;i++){
System.out.println(i+Thread.currentThread().getName());
}
}
}
在主函数中:创建两个不同的线程对象,都让他们执行重写后的run()方法
注意:线程对象.start()是让线程开始工作,线程对象.run()是普通调用方法
public class EasyThreadA {
public static void main(String[] args) {
//main方法是主线程
//实例化线程对象
Thread a=new ThreadA();
Thread b=new ThreadA();
//开启线程,启动线程
a.start();
b.start();
}
}
线程开始后:
两个线程同时进行,就会在控制台上交错输出for()循环中的内容
如果不使两个线程同时启动,即用两个不同对象调用start()方法,而是分别调用出类ThreadA中的run()方法,那么将会出现下面的情况:
public static void main(String[] args) {
//main方法是主线程
//实例化线程对象
Thread a=new ThreadA();
Thread b=new ThreadA();
//这里是普通对象调用方法,会按顺序执行
a.run();
b.run();
}
5.线程的常见方法:
1.sleep()方法
sleep是一个Thread静态方法
写一个测试方法threadSleep
public static void threadSleep() throws InterruptedException {
System.out.println("1-----");
Thread.sleep(5000);
//让运行到该行代码的线程休眠五秒(5000ms)
//休眠后会自动启动线程
System.out.println("2-----");
}
public static void main(String[] args) throws Exception {
threadSleep();
}
执行方法后,会先输出一个1------,sleep5秒(5000毫秒)后,再输出一个2------
写一个方法threadBSleep():
创建一个线程子类ThreadB:
在主函数中调用threadBSleep()方法,会创建出子类对象并开启线程
public static void threadBSleep(){
Thread t = new ThreadB();
t.start();
}
public static void main(String[] args) throws Exception {
threadBSleep();
}
class ThreadB extends Thread{
@Override
public void run() {
for (int i=0;i<=20;i++){
if (i%8==0){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(i+this.getName());
}
}
}
输出0~20的值+线程的名字Thread-0或Thread-1,每到 i%8==0时,线程会sleep2000毫秒,然后继续执行。
2.currentThread()方法 :获取当前线程对象
public static void current(){
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) throws Exception {
current();
}
这时会输出现在的线程对象:
3.setPriority()方法: 设置优先级
优先级越高,获取cpu资源的几率越大。并不是优先级越高就优先运行该线程。
优先级的范围是从1~10,默认是5
注意:优先级范围不能扩容,设置其他参数会报错(0,11)
public static void priority(){
Thread a = new ThreadB();
Thread b = new ThreadB();
//设置线程优先级
a.setPriority(4);
b.setPriority(6);
a.start();
b.start();
}
4.yeild()方法:礼让
礼让的是CPU资源,作用:让出CPU资源,让CPU重新分配,防止一条线程长时间占用CPU资源,达到CPU资源合理分配的结果
注意:sleep()方法也有这种效果
写一个线程子类继承Thread类:
class ThreadC extends Thread{
@Override
public void run() {
for (int i=0;i<=20;i++){
if (i%3==0){
System.out.println(this.getName()+"执行了礼让方法");
Thread.yield();
}
System.out.println(i+this.getName());
}
}
}
//在另一个类中写下面的方法:创建两个子类对象
public static void threadYeild(){
Thread a = new ThreadC();
Thread b = new ThreadC();
a.start();
b.start();
}
//调用方法threadYeild();
public static void main(String[] args) throws Exception{
threadYeild();
}
输出结果为:
5.join()方法:插队(加入)
class ThreadD extends Thread{
private Thread t;
public ThreadD(Thread t){
this.t=t;
}
public ThreadD(){
}
@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) {
throw new RuntimeException(e);
}
}
System.out.println(i+this.getName());
}
}
}
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 {
threadJoin();
}
6.关闭线程:
关闭线程有三种方式:
1.执行stop()方法 但是不推荐
public static void threadStpo() {
Thread a = new ThreadE();
a.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
a.stop();
}
public static void main(String[] args) throws InterruptedException {
threadStpo();
}
class ThreadE extends Thread{
@Override
public void run() {
for (int i=0;i<100;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(i);
}
}
}
2.调用interrupt()方法,设置中断状态,这个先不会中断,我们需要在线程内部判断,中断状态是否被设置,然后进行中断
public static void threadIntertupted(){
Thread a = new ThreadF();
a.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
a.interrupt();
//设置一个中断状态,使程序读取到中断状态,就马上中断程序
}
public static void main(String[] args) throws InterruptedException {
threadIntertupted();
}
class ThreadF extends Thread{
@Override
public void run() {
for (int i=0;i<100;i++){
if (Thread.currentThread().isInterrupted()){
// 在条件判断中判断中断状态,如果是中断状态就中断程序
break;
}
System.out.println(i);
}
}
}
3.自定义一个状态属性,在线程外部设置此属性,影响线程内部的运行
public static void stopThread(){
ThreadG a =new ThreadG();
a.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
a.stop=true;
System.out.println("设置关闭");
}
public static void main(String[] args) throws InterruptedException {
stopThread();
}
class ThreadG extends Thread{
volatile boolean stop=false;
//volatile 直接读取对象里面的数据,不去读取副本里面的数据
@Override
public void run() {
while (!stop){
//System.out.println("A");
}
}
}
6.volatile关键字
volatile 直接读取对象里面的数据,不去读取副本里面的数据
1.保证可见性,不保证原子性
2.禁止指令重排
7.线程安全
线程安全:多个线程操作一个对象,不会出现结果错乱的情况(缺失了部分数据) StringBuffer是线程安全的
线程不安全 StringBuilder就是线程不安全的,数据错乱(数据缺失)
下面为演示StringBuilder是线程不安全的实例:
//实现Runnable接口
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");
}
}
}
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());
}
}
正常情况下(线程安全时),两个线程分别进行,都会存入1000个长度到StringBuilder中,最终输出结果应该是2000,但是这种情况下线程不安全,输出会<2000的值(两千个字符空,有时也一个线程对象刚存完字符,另一个线程对象又在这个位置存入字符,就会导致数据缺失)
8.synchronized关键字
要做到线程安全,我们可以使用synchronized关键字对方法或者代码块加锁,达到线程同步的效果(一个线程一个线程地执行)使用synchronized关键字修饰的方法或代码块,同一时间内只允许一个线程执行此代码
public class SyncThreadB {
//synchronized修饰代码块
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());
}
}
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();
}
}
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) {
throw new RuntimeException(e);
}
}
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();
}
}
9.锁对象
锁对象 使用synchronized需要指定锁对象
synchronized修饰方法:
修饰成员方法:this(当前对象)
修饰静态方法(类的类对象) obj.getClass() Easy.class
10.锁的分类
根据有无锁对象,分为悲观锁和乐观锁 悲观锁有锁对象(只要是锁就是悲观锁) 乐观锁没有锁对象 synchronized是悲观锁 乐观锁的实现方式:CAS 和 版本号控制
还分为公平锁,非公平锁 公平锁就是排队执行,先来后到 Java是非公平锁
可重入锁,非可重入锁 在同步代码块中遇到相同的锁对象的同步代码块,不需要再获取锁对象的权限,直接进入执行 Java中全是可重入锁
根据线程的状态不同(是相对的)又分为偏向锁,轻量级锁(自旋锁),重量级锁
11.CAS
乐观锁(CAS)是一种无锁算法,用于实现多线程之间的变量同步。它不使用锁,而是通过比较当前值和传入值来更新数据,如果一样则更新,否则返回失败状态
12.版本号控制
版本号控制是另一种方式。每当一个线程要修改数据时,都会先读取当前的版本号或时间戳,并将其保存下来。 线程完成修改后,会再次读取当前的版本号或时间戳,如果发现已经变化,则说明有其他线程对数据进行了修改,此时需要回滚并重试。
13.BIO
BIO是一种同步阻塞I/O模式,服务器实现模式是一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理
14.NIO
NIO是一种同步非阻塞的I/O模型,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理
15.AIO
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由OS先完成了再通知服务器应用去启动线程进行处理