序列化、打印流(IO补充)、线程

第一章 序列化

1.概述

java提供了一种对象序列化的机制。用一个字节序列表示一个对象,该字节序列包含对象的数据、对象的类型、对象的属性等..... 字节写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,字节序列还可以从文件中读取回来,重构对象。进行反序列化。对象的数据、类型、属性等信息,都可以用来在内存中创建对象。

2.ObjectOutputStream类 序列化流

可以将java对象的原始数据类型写出到文件,实现对象的持久存储。

序列化操作:相当于是进行加密和解密,让其非正常显示。

        1.一个对象想要被序列化,必须满足两个条件

                1.1: 该类必须实现java.io.Serializable接口,Serializable接口是一个标记接口如果不实现这个接口,不能被序列化或者反序列化,会抛出NotSerializableException异常。

                1.2: 该类的所有属性必须是能序列化的,如果由一个属性不需要可序列化,则该属性必须注明是瞬态的,使用transient关键字修饰;

                瞬态:瞬态属性通常存储临时状态或者临时计算结果,不需要持久存储。

public class Demo01 {
    public static void main(String[] args) throws Exception {
        /**
         * ObjectOutputStream 序列化流构造方法
         */
        FileOutputStream fileOutputStream=new FileOutputStream("a.txt");
        ObjectOutputStream out=new ObjectOutputStream(fileOutputStream);
        Student student=new Student("zhang3","哈尔滨",33);
        out.writeObject(student);//序列化,写入文件
        out.close();
        fileOutputStream.close();
    }
}

3.ObjectInputStream类 反序列化流

public class Demo02 {
    /**
     * ObjectInputStream 反序列化流
     */
public static void main(String[]args)throws Exception{

        Student s=null;
        FileInputStream fileInputStream= new FileInputStream("a.txt");
        ObjectInputStream in=new ObjectInputStream(fileInputStream);
//        读一个对象
        s= (Student) in.readObject();
        in.close();
        fileInputStream.close();
        System.out.println("s = "+s);
    }
}

第二章 打印流

平时控制台打印输出,都是调用print或者println方法,这两个方法都来自于java.io.PrintStream类,该类能够方便的打印各种数据类型的值。是一种便捷输出

public class Demo03 {
    public static void main(String[] args) throws Exception {
        /**
         * PrintStream类  构造方法
         */
        PrintStream printStream = new PrintStream("a.txt");
        /**
         * System.out  就是PrintStream类  只不过他的流向是系统规定的流向控制台,可用set修改
         */
        System.out.println("97");

//      设置系统的打印流走向 , 输出到a.txt;
        System.setOut(printStream);
        System.out.println(96);
        System.out.println(96);

    }
}

上述代码会将 '97' 输出到控制台,将两个 '96' 写到  “a.txt” 文件中,因为用了System.setOut(printStream);改变流向。

第三章 多线程

之前写代码 如果没有跳转语句都是从上到下的执行,那么现在想要设计一个程序, 边打游戏边听歌,怎么设计?

解决上面问题,就要使用多线程或者多进程解决。

 1.并发与并行。

- 并行:指两件或者多个事件同一时刻发生(同时执行)。

- 并发:指两件或者多个事情同一个时间段内发生(交替执行)。

 2.进程和线程

- 进程:指内存中运行的应用程序,一个应用程序可以同时运行多个进程,进程也是程序的依次执行的过程。是系统运行程序的基本单位。系统运行一个程序就是进程创建、运行、消亡的过程。

- 线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程。一个进程如果有多个线程,这个应用程序就称之为多线程程序。

 3.线程类

在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。

       java 使用java.lang.Thread 类代表线程,所有线程对象都必须是Thread或者其子类的实例。

每个线程的作用是完成一定的任务,其实就是执行一段程序或者代码。

java中通过继承Thread类来创建并启动多线程。

步骤:

        1.定义Thread类的子类,重写该类中的run方法,run方法的方法体中内容就代表了线程需要完成的任务。

         2.创建Thread子类的实例,也就是创建了线程对象。

         3.调用线程对象的start方法启动线程。

public class MyThread extends Thread{

//    定义指定线程名称的构造方法
    public MyThread(String name){
//    调用父类的String参数的构造方法,可以执行线程名称
        super(name);
    }
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"正在运行"+i);

        }
    }
}
public class Demo04 {
    public static void main(String[] args) {
    MyThread myThread=new MyThread("我的线程");
        myThread.start();
        for (int i = 0; i < 99; i++) {
            System.out.println("main线程"+i);
        }
    }
}

打印输出“我的线程”和“main线程”交替打印

4.Thread类Api

Thread类:

  构造方法:

        * public Thread(): 分配一个新的线程对象

        * public Thread(String name): 分配一个指定名字的新的线程对象

        * public Thread(Runnable target): 分配一个带有指定目标的新的线程对象

        * public Thread(Runnable target,String name) :分配一个带有指定目标的新的线程对象并指定名字

   常用方法:

        * public String getName(): 获取当前线程的名字

        * public void start(): 让此线程开始执行

        * public void run(): 此线程执行任务的代码在此定义

        * public static void sleep(long millis): 当前在在执行的线程以指定的毫秒数暂停

        * public static Thread currentThread(): 返回对当前正在执行的线程对象。

    5.实现接口方式创建线程

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 99; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);

        }
    }
}
public class Demo06 {
    public static void main(String[] args) {
//        创建自定义类对象    线程任务对象
        MyRunnable mr =new MyRunnable();
//        创建线程对象
        Thread t =new Thread(mr,"小强");
//        开启线程
        t.start();
        for (int i = 0; i < 99; i++) {
            System.out.println("main"+i);
        }
    }
}

6.匿名内部类方式


/**
 * 使用匿名内部类方式  方便创建对象或实现接口
 */
public class Demo07 {
    public static void main(String[] args) {
        Runnable r=
           new Runnable(){
            public void run(){
                for (int i = 0; i <99 ; i++) {
                    System.out.println("kfc"+i);
                }
            }
        };
//        少用一个变量承接
        new Thread(new Runnable(){
            public void run(){
                for (int i = 0; i <99 ; i++) {
                    System.out.println("wwwwwww"+i);
                }
            }
        }).start();
//        main线程
        for (int i = 0; i <99 ; i++) {
            System.out.println("main线程"+i);

        }
    }
}

第四章 线程安全

1.线程安全概述

比如 现在有多个线程同时运行,所有线程都需要操作同样的变量。如果程序每次运行结果和单线程运行结果是一样的,而且变量的值也和预期一样,就是线程安全的。

代码模拟四个窗口同时售卖100张票:

public class MyRunnalbe01 implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
//        窗口永远开启
        while (true) {
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
            }
        }
    }
}
public class Demo08 {
    public static void main(String[] args) {
//        创建线程任务对象
        MyRunnalbe01 myRunnalbe01 =new MyRunnalbe01();
//        创建4个窗口对象
        Thread  t1 =new Thread(myRunnalbe01,"窗口01");
        Thread  t2 =new Thread(myRunnalbe01,"窗口02");
        Thread  t3 =new Thread(myRunnalbe01,"窗口03");
        Thread  t4 =new Thread(myRunnalbe01,"窗口04");

//        同时卖票
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

会发现结果打印出现多个窗口卖了同一张票的情况:

窗口02正在卖:10

窗口04正在卖:10

窗口04正在卖:7

窗口01正在卖:7

窗口02正在卖:7

窗口03正在卖:7

发现程序出了问题,这种问题称为线程不安全。

线程安全问题 都是由全局变量或者静态变量引起的。若每个线程对变量只是读操作,没有写操作,一般来说都是安全的。如果多个线程同时执行写操作,就要考虑线程安全。

2.线程同步

如果要解决多线程并发访问一个资源的安全性问题,也就是票存在还是不存在的问题 ,java中提供了同步机制(synchronized)来解决。

根据案例简述:

例如窗口1线程进入操作的时候,窗口2和窗口3只能在外等待,窗口1结束,窗口1,2,3才有机会去执行,也就是说某个线程修改共享资源时,其他线程不能修改这个资源。等修改完毕之后,才能去抢夺资源。

上述问题有三种解决方法:

1.同步代码块。

2.同步方法。

3.锁机制。

3.同步代码块

- 同步代码块:sychronized 关键字 可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

- 格式:

synchronized(同步锁){

        需要同步操作的代码

}

同步锁:对象的同步锁是一个概念,可以想象为在对象上标记了一个锁。

1.锁对象 可以是任意类型。

2.多个线程对象,要使用同一把锁。

任何时候,最多只需一个线程拥有同步锁,谁拿到锁谁就能进入到代码块,其他的线程只能在外等待(Blocked)

使用同步代码块解决代码:

public class MyRunnalbe01 implements Runnable {
    private int ticket = 100;
    Object lock =new Object();
    @Override
    public void run() {
        // 窗口永远开启
        while (true) {
            synchronized (lock){
                if (ticket > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
                }
            }
        }
    }
}

4.同步方法:

- 同步方法:使用synchronized修饰的方法叫做同步方法,保证一个线程执行该方法的时候 其他线程只能在方法外等待,

public class MyRunnalbe02 implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            sellTicket();
        }
    }
    private synchronized void sellTicket() {
        if (ticket > 0) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
        }
    }
}

5.Lock锁

Lock机制 提供了一个比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块、同步方法具有的功能Lock都有,除此之外更加强大,更体现了面向对象。

Lock锁也叫做同步锁,加锁和释放锁方法化了,

- public void lock(): 加同步锁

- public void unlock();释放同步锁

public class MyRunnalbe03 implements Runnable {
    private int ticket = 100;
    Lock lock=new ReentrantLock();
    @Override
    public void run() {
//        窗口永远开启
        while (true) {
            lock.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
            }
            lock.unlock();
        }
    }
}

第五章 线程状态

1.概述

在线程的生命周期中一共有6种状态:

        * new(新建)刚被创建还没启动

        * Runnable(可运行状态)线程在java虚拟机中的运行状态,可能在运行自己的代码,也可能没有,取决于操作系统。

         * Blocked (锁阻塞)当一个线程试图获取一个对象锁,但是对象锁被其他线程持有,则该线程进入所阻塞状态。持有锁的线程是Runnable状态。

         *Waiting(无限等待)一个线程在等待另一个线程执行一个唤醒动作,该线程进入  Waiting状态。这个状态不能自动唤醒,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒

        * TimedWaiting(记时等待) 同waiting状态,这个状态一直保存到唤醒动作或者超时。         *Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获到异常终止了run方法而死亡。

2.睡眠sleep方法

public static void sleep(long time) 让当前线程进入到睡眠状态,到毫秒值后自动醒来继续执行

3.等待和唤醒

Object类的方法

public void wait():让当前线程进入到等待状态,这个方法必须锁对象调用。

public class Demo09 {
    public static void main(String[] args) throws InterruptedException {
//    步骤1:子线程开启,进入无限等待状态,如果没有被唤醒  将无法继续运行。
        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("beagin wait ....");
                    synchronized ("") {
                        "".wait();
                    }
                    System.out.println("over");
                } catch (Exception e) {

                }
            }
        };
        new Thread(r).start();
//    步骤2:加入如下代码,3秒之后会执行notify方法,唤醒wait中的线程
        Thread.sleep(3000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("") {
                    System.out.println("唤醒");
                    "".notify();
                }
            }
        }).start();
    }
}

4.多线程小练习

题目要求:利用多线程求解某范围素数,每个线程负责 1000范围:线程1找1-1000;线 程 2 找 1001-2000;线程 3 找 2001-3000。编程程序将每个线程找到的素数及时打印。所有的素数输出完毕后,再输出素数的总数;

分析:由题可知需要一个类实现Runnable接口,在重写run方法中表明查找素数需求且传入参数为线程负责范围,因为三线程负责范围没有交集,所以不需要锁对象。

public class FindPrimesTask implements Runnable{
    private int start;
    private int end;
    // 创建一个集合用于存储各线程找到的素数
    List<Integer> primeNumbers = new ArrayList<>();
    // 定义一个任务,用于查找指定范围内的素数并将其添加到集合中
    public FindPrimesTask(int start, int end) {
        this.start = start;
        this.end = end;
    }
    @Override
    public void run() {
        for (int num = start; num <= end; num++) {
            if (isPrime(num)) {
                System.out.println(Thread.currentThread().getName()+"找到素数"+num);
                primeNumbers.add(num);
            }
        }
        System.out.println(Thread.currentThread().getName()+"素数总数:" + primeNumbers.size());
    }
    // 判断一个数是否为素数
    public static boolean isPrime(int num) {
        if (num < 2) {
            return false;
        }
        for (int i = 2; i <= Math.sqrt(num); i++) {
            if (num % i == 0) {
                return false;
            }
        }
        return true;
    }
}
public class Demo10 {
    public static void main(String[] args) {

        // 创建三个线程,每个线程负责1000范围内的素数查找
        FindPrimesTask task1 = new FindPrimesTask(1, 1000);
        FindPrimesTask task2 = new FindPrimesTask(1001, 2000);
        FindPrimesTask task3 = new FindPrimesTask(2001, 3000);

        // 启动线程
        Thread thread1 = new Thread(task1,"线程1");
        Thread thread2 = new Thread(task2,"线程2");
        Thread thread3 = new Thread(task3,"线程3");

        // 等待所有线程完成任务
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值