Java学习笔记-基础语法Ⅹ-进程线程
阅读原文时间:2023年07月08日阅读:2

学习快一个月了,现在学到了黑马Java教程的300集


打印流的特点:

  • 只负责输出数据,不负责读取数据
  • 有自己的特有方法

字节打印流:PrintStream,使用指定的文件名创建新的打印流

import java.io.FileNotFoundException;
import java.io.PrintStream;

public class Demo {
    public static void main(String[] args) throws FileNotFoundException {
        PrintStream ps = new PrintStream("test.txt");
        ps.write(97);
        ps.println(97);
        ps.close();
    }
}

字符打印流:PrintWriter

import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class Test {
    public static void main(String[] args) throws IOException {
        // 错误示例
//        PrintWriter pw = new PrintWriter("demo.txt",true);
        PrintWriter pw = new PrintWriter(new FileWriter("demo.txt"),true);
        pw.println(97);
        pw.close();
    }
}

对象序列化流

对象序列化:将对象保存到磁盘中,或者在网络中传输对象,即使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据等,字节序列写到文件之后,相当于文件中持久保存了一个对象的信息

字节序列还可以从文件中读取回来,重构对象,把他进行反序列化

对象序列化流:ObjectOutputStream

  • 将Java对象的原始数据类型和图形写入OutputStream,可以使用ObjectInputStream读取(重构)对象,通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机或进程中重构对象

构造方法:ObjectOutputStream(OutputSteram out):创建一个写入指定的OutputStream的ObjectOutputStream

注意:

  • 一个对象想要被序列化,该对象所属的类必须实现Serializable接口

  • Serializable是一个标记接口,实现该接口,不需要重写任何方法

    import java.io.*;

    public class Demo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    Student s1 = new Student("林青霞",19);
    Student s2 = new Student("张曼玉",18);
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("My_File/src/demo19/demo.txt"));
    oos.writeObject(s1);
    oos.writeObject(s2);
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("My_File/src/demo19/demo.txt"));
    Object o = ois.readObject();
    System.out.println(o);
    // 可以用循环改进
    o = ois.readObject();
    System.out.println(o);
    }
    }

对象序列化流常见问题

  • 用对象序列化流序列化一个对象后,如果修改了对象所属的类文件,读取数据会出现问题,抛出IncalidClassException

  • 解决办法:给对象所属的类加一个serialVersionUID,private static final long serialVersionUID = 42L

  • 如果一个对象中的某个成员变量的值不想被序列化,可以给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

玩游戏次数

// 玩游戏的类,静态方法可直接调用
import java.util.Random;
import java.util.Scanner;

public class PlayGames {
    public static void play(){
        System.out.println("猜数,请输入一个小于100的数:");
        Random r = new Random();
        int i = r.nextInt(100);
        Scanner sc = new Scanner(System.in);
        int number = sc.nextInt();
        while(number!=i){
            if(number>i){
                System.out.println("大了,请再猜:");
            }else if(number<i){
                System.out.println("小了。请再猜");
            }
            number = sc.nextInt();
        }
        System.out.println("恭喜你猜对了,数字为"+i);
    }
}


// main函数
import java.io.*;
import java.util.Properties;

public class Properties_demo {
    public static void main(String[] args) throws IOException {
        Properties pt = new Properties();
        File file = new File("games.txt");
        FileReader fr = new FileReader(file);
        pt.load(fr);
        int count = Integer.parseInt(pt.getProperty("count"));
        if(count<3){
            PlayGames.play();
            count++;
            pt.setProperty("count",String.valueOf(count));
            FileWriter fw = new FileWriter("games.txt");
            pt.store(fw,"hello");
        }else{
            System.out.println("你的游戏次数已用完,请充值!");
        }

    }
}


// 初始状态
// games.txt
count=0

进程&线程

进程:正在运行的程序,是系统进行资源分配和调用的独立单位,并且每一个进程都有它自己的内存空间和系统资源

线程:是进程中的单个顺序控制流,是一条执行路径

某类要实现多线程的话,需要继承Thread,然后在该类中重写run方法,重写run方法的目的是用来封装被线程执行的代码,但是直接调用的话,相当于普通方法的调用,如果想启动线程,则需要使用start方法启动,然后由JVM调用此线程的run方法

public class MyThread extends Thread{
    @Override
    public void run() {
        for(int i = 0;i<1000;i++){
            System.out.println(getName()+","+i);
        }
    }
}


public class My_thread {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        mt1.start();
        mt2.start();
        mt1.setName("1111");
        mt2.setName("2222");
    }
}

获取线程名称用getName,设置线程名称用setName,也可以一开始就用带参构造方法设置线程名称,同时还可以调用Thread类中的静态方法currentThread来获取当前正在执行的线程

线程调度:

  • 分时调度:轮流使用
  • 抢占式调度:有限让优先级高的线程使用CPU

Java使用抢占式调度模型

Thread类中设置和获取线程优先级:

  • getPriority
  • setPriority

线程优先级高仅仅表示线程获取的CPU时间片的几率高

线程控制主要有3个方法:

  • sleep:设置睡眠时间
  • join:等待线程死亡
  • setDaemon:守护线程

join的用法:

public class MyThread extends Thread{
    @Override
    public void run() {
        for(int i = 0;i<1000;i++){
            System.out.println(getName()+","+i);
        }
    }
}


public class My_thread2 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();
        // 注意join在start之后才生效
//        mt1.join();
        mt1.start();
        mt1.join();
        mt2.start();
        mt3.start();

    }
}

在使用setDaemon的时候,自己尝试了与黑马视频很多不同的代码,发现有意思的地方

public class MyThread extends Thread{
    @Override
    public void run() {
        for(int i = 0;i<100;i++){
//            System.out.println(getName());
            System.out.println(getName()+","+i);
        }
    }
}


public class My_thread3 {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        mt1.setName("关羽");
        mt2.setName("张飞");
        mt1.setDaemon(true);
        mt2.setDaemon(true);
        Thread.currentThread().setName("刘备");
        mt1.start();
        mt2.start();

        for(int i = 0;i<10;i++){
            System.out.println(Thread.currentThread().getName());
//            System.out.println(Thread.currentThread().getName()+","+i);
        }
    }
}

如果继承Thread的类简洁写法如从System.out.println(getName()+","+i);换到System.out.println(getName());并且main函数中也是简洁写法,那么可以达到守护的效果,但是如果继承Thread的类是不简洁写法System.out.println(getName()+","+i);,而main函数中是简洁写法System.out.println(Thread.currentThread().getName());,那么输出只会是main函数中的内容。

个人猜测是非简洁写法本身就抢不过简洁写法,那么只有让非守护线程的main函数执行,执行完毕后又只有守护线程,所以就结束了

多线程实现方式Ⅱ

除了继承Thread,还可以实现Runnable,相比于继承Thread,接口方法好处在于:

  • 避免Java单继承的局限性

  • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离

    public class runnableDemo implements Runnable{
    @Override
    public void run() {
    for(int i = 0;i<100;i++){
    System.out.println(Thread.currentThread().getName()+","+i);
    }
    }
    }

    public class ThreadDemo {
    public static void main(String[] args) {
    runnableDemo rd1 = new runnableDemo();
    Thread t1 = new Thread(rd1,"飞机");
    Thread t2 = new Thread(rd1,"高铁");
    t1.start();
    t2.start();
    }
    }

卖票案例

描述:有100张票,3个窗口售卖,要做到同步

方法Ⅰ:

// Ticket类
public class Ticket implements Runnable {
    private int ticket = 100;
    private boolean flag = true;

    @Override
    public void run() {
        while (true) {
            if(flag) {
                flag = false;
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "票");
                    ticket--;
                }
                flag = true;
            }
        }
    }
}


// 测试类
public class SellDemo {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t,"窗口1");
        Thread t2 = new Thread(t,"窗口2");
        Thread t3 = new Thread(t,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

这样做不可以达到效果,因为原子锁比较复杂,不能直接用if语句实现,要用Java提供的API接口

Java提供了同步方法

public class TicketSynchronize implements Runnable{
    private int tickets = 100;
    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj){
                if(tickets>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在售出第"+tickets+"票");
                    tickets--;
                }
            }
        }
    }
}


public class Demo {
    public static void main(String[] args) {
        TicketSynchronize ts = new TicketSynchronize();
        Thread t1 = new Thread(ts,"窗口1");
        Thread t2 = new Thread(ts,"窗口2");
        Thread t3 = new Thread(ts,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

上面内容中,如果一直是一个线程,那么把票数从100加到1000,可能是因为synchronized是一个重型锁,锁的时间比较长

同步的好处&弊端:

  • 好处:解决多线程的数据安全问题
  • 弊端:当线程很多时,每个线程都会去判断同步上的锁,耗费资源

同步方法

同步方法时,锁的对象要从原来的Object换成this

同步静态方法时,锁对象是类名.class

线程安全的类:

  • StringBuffer:线程安全,可变的字符序列,功能同StringBuilder
  • Vector:线程安全,功能同ArrayList
  • Hashtable:线程安全,功能同HashMap

Lock

比synchronized方法提供更广泛的锁定操作,允许更灵活的操作

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable{
    private int tickets = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            lock.lock();
            try{
                if(tickets>0){
                    System.out.println(Thread.currentThread().getName()+"正在卖出第"+tickets+"张票");
                    tickets--;
                }
            }
            finally {
                lock.unlock();
            }
        }
    }
}


public class Demo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

生产者消费者案例

public class Box {
    private int milk;
    private boolean state = false;
    public synchronized void put(int milk){
        if(state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.milk = milk;
        System.out.println("送奶工将第"+this.milk+"瓶奶送入奶箱");
        state = true;
        notifyAll();
    }
    public synchronized void get(){
        if(!state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("用户拿到第"+this.milk+"瓶奶");
        state = false;
        notifyAll();
    }
}


public class Producer implements Runnable{
    private Box b;
    public Producer(Box b) {
        this.b = b;
    }

    @Override
    public void run() {
        for(int i = 0;i<5;i++){
            b.put(i);
        }
    }
}


public class Consumer implements Runnable{
    private Box b;
    public Consumer(Box b) {
        this.b = b;
    }

    @Override
    public void run() {
        while(true){
            b.get();
        }
    }
}


public class BoxDemo {
    public static void main(String[] args) {
        Box b = new Box();
        Producer p = new Producer(b);
        Consumer c = new Consumer(b);
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}