Java学习笔记9

今日内容:

1.序列化  反序列化   序列化版本控制    transient关键   禁止序列化   序列化对象,对象的属性也必须是可序列化的

 2.线程  Thread    常用方法    run   start   sleep   currentThread  yield   join  setPriority  1-10  5    

中断线程    stop   interrupt设置中断状态    isInterrupted判断中断状态    自定义变量   volatile

3.线程生命周期    

4.线程安全   同步   synchronized   方法  代码块     锁对象是什么    同步原理   锁的分类

序列化

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.txt");

        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();

        } finally {

            if (ois != null) {

                try {

                    ois.close();

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

            if (fis != null) {

                try {

                    fis.close();

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

        }

        return null;

    }

    写出的方法    

public static void writeStudent(Student stu) {

        FileOutputStream fos = null;

        ObjectOutputStream oos = null;

        File file = new File("D://student.txt");

        if (!file.exists()) {

            try {

                file.createNewFile();

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

        try {

            fos = new FileOutputStream(file);

            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();

                }

            }

        }

}

对象类 需要对其设置setget方法和两个toString方法其中一个toString方法是空的

public class Student implements Serializable {

    //修改版本号

    private static final long serialVersionUID = 1L;



    private String name;

    //transient禁止属性的值被序列化,如果这行代码未被注释掉则该属性无法序列化

    private transient String sex;

    private double score;

}

线程

线程和进程是操作系统中的两个重要概念,它们在程序执行和资源管理方面扮演着不同的角色。以下是关于线程和进程的详细解释:

线程

定义:

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。线程是CPU调度和分派的基本单位,它是比进程更独立调度的单位。

特性:

线程是进程的一个执行流,一个进程可以包含多个线程。

线程之间共享进程的所有资源,包括内存、文件描述符等,但每个线程拥有自己独立的程序计数器、虚拟机栈、本地方法栈等。

线程由CPU独立调度执行,在多CPU环境下允许多个线程同时运行。

线程间的通信更加方便和快捷,因为它们共享进程的地址空间。

线程的创建和销毁开销较小,切换也更为快速。

进程

定义:

进程是资源分配的基本单位,它是程序执行时的一个实例,在程序运行时创建。进程拥有独立的内存空间、文件描述符等资源,并由操作系统进行分配和管理。

特性:

进程是程序的一次执行过程,或是正在运行的一个程序。它由程序、数据和进程控制块三部分组成。

每个进程都有自己独立的地址空间,包括文本区域、数据区域和堆栈区域。

进程之间的通信需要通过显式的机制,如管道、消息队列和共享内存等来实现。

进程切换开销较大,因为需要保存和恢复整个进程的上下文。

进程的管理相对复杂,需要操作系统的支持。

线程与进程的区别

资源占用:进程是资源分配的基本单位,拥有独立的内存空间和系统资源;而线程是CPU调度的基本单位,共享进程的资源。

独立性:进程之间相对独立,每个进程都有自己的代码、数据和堆栈空间;线程是进程内的一个执行单元,共享父进程的资源。

通信方式:进程之间的通信需要通过显式的机制,如管道、消息队列等;线程之间的通信则更加方便和快捷,因为它们共享进程的地址空间。

开销:进程切换开销较大,因为需要保存和恢复整个进程的上下文;线程切换开销较小,因为它们共享进程的很多资源。

管理方式:进程的管理相对复杂,需要操作系统的支持;线程的管理更为灵活,可以更简单地创建和销毁。

综上所述,线程和进程在实现并发和并行操作方面各有优势。了解它们之间的区别对于编写高效、安全和可靠的程序至关重要。

线程:

程序运行阶段不同的运行路线

Thread线程类

自定义线程   继承  Thread   

 public static void main(String[] args) {

        //实例化线程对象

        Thread a= new ThreadA();

        Thread b= new ThreadA();

        //开启线程

        a.start();

        b.start();


//.run只是一个普通对象调用方法不是线程他会先执行一个然后再执行另外一个不会同时启动

        a.run();

        b.run();

    }

}
class ThreadA extends Thread {



    //重写run方法,定义线程要执行的任务

    @Override

    public void run(){

        for(int i=0; i<=20; i++){

            System.out.println(i+this.getName());

        }

  }

对于线程类的设置,每次都需要重写run方法

线程中常用的方法

休眠 sleep

public static void threadSleep() throws InterruptedException {

        //sleep 是一个Thread类的静态方法

        System.out.println("1------------");

        //让运行到该行代码的线程休眠五秒

        //休眠后会自动启动线程

        Thread.sleep(5000);

        System.out.println("2------------");
}

Thread.sleep()是休眠的语句

获取当前线程对象

    Thread.currentThread();

是 Java 中的一个静态方法,用于返回对当前正在执行的线程对象的引用。

简单使用

public class CurrentThreadExample {  

    public static void main(String[] args) {  

        // 主线程  

        System.out.println("Main thread name: " + Thread.currentThread().getName());  

        // 创建一个新线程  

        Thread newThread = new Thread(() -> {  

            // 在新线程中执行  

            System.out.println("New thread name: " + Thread.currentThread().getName());  

        });  

        // 启动新线程  

        newThread.start();  

    }  
}

优先级的设置 .setPrioity

 

public static void priority() {

        Thread a = new ThreadB();

        Thread b = new ThreadB();

        //设置优先级

        a.setPriority(1);

        b.setPriority(6);

        //优先级越高,获取cpu资源的几率越大

        //优先级最小是1,最大是10,默认值是5。设置其他值报错。

        a.start();

        b.start();

    }

礼让 yeild

作用:让出cpu资源,让CPU重新分配资源,分配给谁不确定

目的:防止一条线程长时间占用CPU的资源,让CPU资源进行合理的分配

用sleep(0)也可以达到类似的效果

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());

        }

    }

}

Join 是成员方法 加入(插队)

在A线程中执行力B.join()方法,B线程执行完毕后A线程再运行。

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) {

                    throw new RuntimeException(e);

                }

            }

            System.out.println(i + this.getName());

        }

    }

}

线程关闭

方法一 执行stop方法,不推荐

private static void threadStop() {

        Thread a = new ThreadE();

        a.start();

        try {

            Thread.sleep(2000);

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

        a.stop();

    }
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);

        }

    }

}

用main方法直接调用threadStop();

方法二 调用interrupt()设置中断状态,这个线程不会中断,我们需要在线程内部判断,中断状态是否被设置,然后执行中断操作

private static void threadInterrupted() {

        Thread a = new ThreadF();

        a.start();

        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

        a.interrupt();

    }
class ThreadF extends Thread{

    @Override

    public void run() {

        for(int i=0;i<1000;i++){

            if (Thread.currentThread().isInterrupted()){

                break;

            }

            System.out.println(i);

        }

    }

}

如何使用 .interrupt()

中断线程:你可以通过调用目标线程的 .interrupt() 方法来中断它。

检查中断状态:被中断的线程可以通过检查 Thread.currentThread().isInterrupted() 方法来检查它是否已经被中断。此外,还可以通过 Thread.interrupted() 方法检查当前线程的中断状态,但这个方法会清除中断状态(即将中断状态设置为 false)。

响应中断:线程应该在其主要循环或等待操作中定期检查中断状态,并在检测到中断时优雅地退出。通常,这涉及到捕获 InterruptedException(由阻塞方法如 Thread.sleep(), Object.wait(), Thread.join() 等抛出)并处理它,通常是通过返回或重新抛出异常。

方法三 自定义一个状态属性,在线程外部设置此属性,影响线程内部的运行

private static void stopThread() {

        ThreadG a = new ThreadG();

        a.start();

        try {

            Thread.sleep(100);

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

        a.stop = true;
}
class ThreadG extends Thread {

    volatile boolean stop = false;



    @Override

    public void run() {

        while (!stop) {

            System.out.println("A");

        }

    }

}

volatile

作用:

可见性:当一个变量被声明为 volatile 之后,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,而不是使用自己工作内存中的缓存值。这样就保证了变量对所有线程的可见性。

有序性:在Java虚拟机中,为了提高程序运行效率,编译器和处理器会对指令进行重排序。但是,在多线程环境中,重排序可能会导致数据不一致性问题。volatile 关键字可以在一定程度上防止这种重排序,从而保证了多线程环境下程序的有序性。需要注意的是,volatile 不能保证复合操作的原子性,例如 i++ 操作。

使用场景

状态标志:volatile 变量可以用作状态标志,控制程序的执行流程。

单例模式中的双重检查锁定(Double-Check Locking):在多线程环境下实现单例模式时,可以使用 volatile 关键字来确保 instance 变量在多个线程之间的可见性,从而避免创建多个实例。

独立观察:volatile 变量也可以用于那些被多个线程访问但不修改的变量,以确保所有线程都看到相同的数据。

线程的生命周期

线程的生命周期是线程从创建到销毁所经历的一系列状态变化过程。在Java等编程语言中,线程的生命周期通常分为以下五个阶段:

1. 新建状态(New)

描述:当使用new关键字创建了线程对象后,该线程就处于新建状态。此时,线程对象已经分配了必要的内存空间,并初始化了其成员变量,但还没有开始执行。

示例:Thread t = new MyThread(); // MyThread是Thread的子类或者实现了Runnable接口

2. 就绪状态(Runnable)

描述:线程对象调用start()方法后,线程就进入了就绪状态。此时,线程已经具备了运行条件,但还没有被分配CPU资源,因此处于等待CPU调度的状态。线程调度由操作系统的线程调度器负责,它会根据一定的算法(如优先级、时间片等)来决定哪个线程将获得CPU资源并执行。

注意:处于就绪状态的线程并不意味着它一定会立即执行,而是需要等待CPU资源的分配。

3. 运行状态(Running)

描述:当就绪状态的线程被调度并获得CPU资源时,它就进入了运行状态。此时,线程将执行其run()方法中的代码。

特点:线程在执行过程中,可能会因为时间片耗尽、执行了阻塞操作(如I/O操作、sleep()、wait()等)等原因而让出CPU资源,从而回到就绪状态或阻塞状态。

4. 阻塞状态(Blocked)

描述:线程在执行过程中,可能会因为某些原因而进入阻塞状态。阻塞状态是线程生命周期中的一个重要阶段,它表示线程暂时停止了执行。

原因:线程进入阻塞状态的原因有多种,如执行了sleep()方法、wait()方法、join()方法、等待I/O操作完成等。

特点:处于阻塞状态的线程不会占用CPU资源,只有当阻塞原因消除后(如睡眠时间结束、I/O操作完成等),线程才会重新进入就绪状态,等待CPU资源的分配。

5. 死亡状态(Terminated)

描述:线程的生命周期的最后一个阶段是死亡状态。当线程执行完毕或因为异常而终止时,它就进入了死亡状态。此时,线程对象所占用的内存空间将被释放,线程将不再执行任何操作。

原因:线程死亡的原因主要有两种:一种是线程正常执行完毕,即run()方法中的代码执行完成;另一种是线程在执行过程中遇到了未捕获的异常或错误,导致线程被强制终止。

总结

线程的生命周期是线程从创建到销毁所经历的一系列状态变化过程,包括新建状态、就绪状态、运行状态、阻塞状态和死亡状态。了解线程的生命周期对于编写高效、稳定的并发程序具有重要意义。在编写并发程序时,需要根据实际情况选择合适的线程状态转换策略,以确保程序的正确性和性能。

线程安全

多个线程操作一个对象,不会出现结果错乱的情况(缺失),这就是线程不安全StringBuilder

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());

    }



//实现Runnable接口

class RunA implements Runnable {

    //需要重写run方法

    StringBuilder strB;



    public RunA(StringBuilder strB) {

        this.strB = strB;

    }



    @Override

    public void run() {

        for (int i = 0; i < 1000; i++) {

            strB.append("0");

        }

    }

}

     

线程安全时输出结果应该为2000,而此时线程不安全导致部分数据丢失所以会出现1168

而将以上程序中的StringBuilder全部换成StringBuffer线程则是安全的结果就会变成

synchronized方法

如果想要解决线程安全,我们可以使用synchronized,对方法或者代码块加锁,达到线程同步的效果。sybchronized的作用是防止多个线程在同一时间访问同一个数据,他会让线程按个进入一个进入之后下一个才能进入

 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());

        }

    }

锁对象

锁对象:使用synchronized需要指定锁对象

synchronized修饰方法   成员方法 this是锁对象

如果是静态方法   类的类对象是锁对象  例如obj.getClass()  Easy.class

锁的分类

    //有无锁对象分为  悲观锁和乐观锁   悲观锁:有锁对象(看门老大爷) 乐观锁:没有锁对象

    //synchronized是悲观锁  Java中说到锁就是悲观锁  乐观锁的实现方式  CAS和版本号控制

    //公平锁和非公平锁   公平锁一视同仁先来后到    非公平锁都有机会没有先来后到

    //可重入锁    Java中全部都是可重入锁  遇到相同的锁对象的同步代码块,不需要再次获取锁对象的权限直接进入执行

    //根据线程的状态不同   偏向锁,轻量级锁(自旋锁),重量级锁

  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值