java学习笔记19(线程、进程、多线程、IO)

java学习笔记19(线程、进程、多线程、IO),第1张

java学习笔记19(线程、进程、多线程、IO)

文章目录
  • 学习内容
    • 多线程
      • 进程
      • 线程
      • 线程与进程
      • 并发和并行
      • 多线程编程
    • java 中的线程
      • 线程调度
        • 分时调度
        • 抢占式调度
      • 创建新线程
        • 第一种方式,利用 `Thread` 类
        • 第二种方式,实现 `Runnable` 接口
        • 第三种方式:匿名内部类
      • 线程的优先级
    • 同步
      • 同步代码块
      • 同步方法
      • Lock 锁机制
    • 线程状态
    • I/0
      • File 类
        • 概述
      • File 类中的静态成员变量
        • 路径
        • 构造方法
        • 常用方法
      • 判断功能的方法
      • 增删的方法
      • 目录的遍历
    • 字节流
      • 字节输出流
        • 常用方法
      • FileOutputStream 类
        • 常用方法:
      • 字节输入流 InputStream
      • FileInputStream 类
        • 读取字节数据
    • 字符流
      • 与字节流区别
  • 任务内容
    • 任务0
    • 任务1 熟悉线程生命周期方法
      • 1-1
      • 1-2
    • 任务2
      • Runnable 类
      • Thread 类:
    • 任务3
    • 任务4
    • 任务5
    • 任务6

学习内容 多线程 进程

在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。

线程

某些进程内部还需要同时执行多个子任务,这些子任务就是线程。

例如:用播放器播放视频时,程序输出视频画面是一个进程、音频是一个进程、字幕是一个进程、显示视频进度是一个进程。

线程是 *** 作系统调度的最小任务单位。

线程与进程

进程和线程是包含关系,但是多任务既可以由多进程实现,也可以由单进程内的多线程实现,还可以混合多进程+多线程。

具体采用哪种方式,要考虑到进程和线程的特点。

和多线程相比,多进程的缺点在于:

  • 创建进程比创建线程开销大,尤其是在Windows系统上;
  • 进程间通信比线程间通信要慢,因为线程间通信就是读写同一个变量,速度很快。

而多进程的优点在于:

  • 多进程稳定性比多线程高,因为在多进程的情况下,一个进程崩溃不会影响其他进程,而在多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。

进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。

并发和并行

并行:在同一时刻,在多个指令在多个cpu同时进行。

并发:在同一时刻,在多个指令在单个cpu交替执行。

多线程编程

Java语言内置了多线程支持:一个Java程序实际上是一个JVM进程,JVM进程用一个主线程来执行main()方法,在main()方法内部,我们又可以启动多个线程。对于大多数Java程序来说,我们说多任务,实际上是说如何使用多线程实现多任务。

和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。

例如,播放视频时时,就必须由三个线程分别播放视频、播放音频、呈现字母,三个线程需要协调运行,否则就会出现如音画不同步、字母不同步等情况。因此,多线程编程的复杂度高,调度更困难。

Java多线程编程的特点又在于:

  • 多线程模型是Java程序最基本的并发模型;
  • 后续读写网络、数据库、Web开发等都依赖Java多线程模型。

因此,必须掌握Java多线程编程才能继续深入学习其他内容。

java 中的线程 线程调度 分时调度

所有线程轮流使用 CPU 的使用权,平均分配每个线程所占用的时间片。

抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对较多一些。

创建新线程

Java用Thread对象表示一个线程。

第一种方式,利用 Thread 类
  1. 创建 Thread 类的子类

  2. 在这个子类中重写 Thread 类的 run() 方法,设置线程任务。

  3. 创建子类的实例对象。

  4. 调用 Thread 类中的 start() 方法,开启这个线程,调用 run() 方法。

注意:一个线程对象只能调用一次 `start()` 方法;

线程的执行代码写在 `run()` 方法中;

线程调度由 *** 作系统决定,程序本身无法决定调度顺序;
第二种方式,实现 Runnable 接口
  1. 定义 Runnable 接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正
    的线程对象。
  3. 调用线程对象的start()方法来启动线程。

Thread.sleep()可以把当前线程暂停一段时间,传入的参数是毫秒。可以通过传参来调整暂停时间的大小。

第三种方式:匿名内部类 线程的优先级

可以对线程设定优先级,设定优先级的方法是:

Thread.setPriority(int n) // 1~10, 默认值5

优先级高的线程被 *** 作系统调度的优先级较高, *** 作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。

同步 同步代码块

synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){
     需要同步 *** 作的代码
}

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

  1. 锁对象 可以是任意类型。
  2. 多个线程对象 要使用同一把锁。
  3. 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程进入阻塞状态等待(BLOCKED)。
同步方法

使用 synchronized 修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着,进入阻塞状态。

格式:

public synchronized void method(){
   可能会产生线程安全问题的代码 
}

同步方法中的同步锁:

  • 对于非static方法,同步锁就是this。
  • 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
Lock 锁机制

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定 *** 作,同步代码块 / 同步方法具有的功能Lock都有,还有更强大的功能。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

public void lock() :加同步锁。

public void unlock() :释放同步锁。

使用步骤:

  1. 先创建Lock对象,

    Lock lock =new ReentrantLock();
    
  2. 在可能出现同步安全问题的代码前调用Lock接口中的方法lock() 获取锁

  3. 在可能出现同步安全问题的代码后调用Lock接口中的方法unlock() 释放锁

线程状态
  • 新建(NEW):新创建了一个线程,但还没调用线程的 start 方法;

  • 运行(RUNNABLE),又包括:

    ​ 就绪(ready):运行线程的 start 方法启动后,线程位于可运行线程池中,等待被调度;

    ​ 运行中(RUNNING):就绪的线程获得 CPU 的时间片就变为运行中。

  • 阻塞(BLOCKED):线程等待获得锁;

  • 等待(WAITING):接收事件通知后或系统中断后进入等待,进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。

  • 超时(TIMED_WAITING):等待指定时间后会自行返回;

  • 终止(TERMINATED):线程已执行完毕;

注意:阻塞(BLOCKED)和等待(WAITING)不用刻意区分,这两者都会暂停线程。

I/0 File 类 概述

文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等 *** 作。

File 类中的静态成员变量

static String pathSeparator :与系统有关的路径分隔符,用来分隔多个路径,在 windows 中是分号 ; 。

static String separator :与系统有关的默认名称分隔符,在 widows 中是反斜杠 。

路径

绝对路径:从盘符开始的路径,这是一个完整的路径。

相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。

构造方法

public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。

public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。

public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。

常用方法

public String getAbsolutePath() :返回此File的绝对路径名字符串。

public String getPath() :将此File转换为路径名字符串。

public String getName() :返回由此File表示的文件或目录的名称。

public long length() :返回由此File表示的文件的长度。

判断功能的方法

public boolean exists() :此File表示的文件或目录是否实际存在。

public boolean isDirectory() :此File表示的是否为目录。

public boolean isFile() :此File表示的是否为文件。

增删的方法

public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。

public boolean delete() :删除由此File表示的文件或目录。

public boolean mkdir() :创建由此File表示的目录。

public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。

目录的遍历

public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。

public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

字节流 字节输出流

java.io.OutputStream 类是一个抽象类,表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字
节输出流的基本共性功能方法。

常用方法

public void close() :关闭此输出流并释放与此流相关联的任何系统资源。当完成流的 *** 作时,必须调用此方法,释放系统资源。

public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。

public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。

public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
public abstract void write(int b) :将指定的字节输出流。

FileOutputStream 类

FileOutputStream 类是 OutputStream 类的子类,它是文件输出流,用于将数据输出到文件。

常用方法:

public void close() :关闭此输出流并释放与此流相关联的任何系统资源。

public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。

public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。

public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。

public abstract void write(int b) :将指定的字节输出流。

字节输入流 InputStream

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

public void close() :关闭此输入流并释放与此流相关联的任何系统资源。当完成流的 *** 作时,必须调用此方法,释放系统资源。

public abstract int read() : 从输入流读取数据的下一个字节。

public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

FileInputStream 类

该类是文件输入流,从文件中读取字节。

读取字节数据
  1. 读取字节: read 方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回 -1 。
  2. 使用字节数组读取: read(byte[] b) ,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读
    取到末尾时,返回 -1。
字符流

内容跟字节流基本差不多,就不复制粘贴了。

要点:输入流的抽象类是Reader,其读取字符文件的子类是FileReader。

输出流的抽象类是Writer,其读取字符文件的子类是FileWrite。

读取、输出的时候,把字节流里的byte[]换成char[]即可。

与字节流区别

字符流,只能 *** 作文本文件,不能 *** 作图片,视频等非文本文件。

当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流

任务内容 任务0

运行后可看看效果,然后尝试着解释为什么结果是这样的。

package com.xxm.advanced_camp.mission10_multithreading;

public class test1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁") {
                    System.out.println("t1 start");
                    try {
// t1 释放锁
                        "锁".wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("t1 end");
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁") {
                    System.out.println("t2 start");
                    try {
// 通知 t1 进入等待队列
                        "锁".notify();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t2 end");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

答:运行结果

t1 start
t2 start
t2 end
t1 end

答:

尝试解释:

JVM先调用主方法 → 调用 t1 线程的 run() 方法 → 创造一个“同步锁” → 执行输出语句,输出 “t1 start” → 进入等待状态,JVM继续执行主方法 → 调用 t2 线程的 run() 方法 → 执行输出语句,输出 “t2 start” → 通知所有“同步锁”的成员,等待中的线程可以继续执行了 → 执行输出语句,输出 “t2 end” →继续执行“同步锁”成员中的等待成员后面的语句,也就是 t1 线程中后面的语句,也就是执行输出语句,输出"t1 end"。

任务1 熟悉线程生命周期方法 1-1

开启四个线程,两个线程调用锁的 wait 方法,另外两个调用锁的 notify 方法,观察执行结果并解释原因。

答:代码如下 :

package com.xxm.advanced_camp.mission10_multithreading.task1;


public class Task1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁") {
                    System.out.println("t1 线程开始");
                    try {
                        "锁".wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("t1 线程结束");
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("lock1") {
                    System.out.println("t2 线程开始");

                    try {
                        "lock1".wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("t2 线程结束");
                }
            }
        });

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁") {
                    System.out.println("t3 线程开始");

                    try {
                        "锁".notify();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t3 线程结束");
                }
            }
        });

        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("lock1") {
                    System.out.println("t4 线程开始");

                    try {
                        "lock1".notify();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t4 线程结束");
                }
            }
        });


        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

执行结果并不唯一,有多种情况

//第一次执行
t1 线程开始
t2 线程开始
t3 线程开始
t4 线程开始
t3 线程结束
t4 线程结束
t1 线程结束
t2 线程结束

//第二次执行
t1 线程开始
t2 线程开始
t3 线程开始
t3 线程结束
t1 线程结束
t4 线程开始
t4 线程结束
t2 线程结束

  
//第三次执行
t2 线程开始
t1 线程开始
t4 线程开始
t3 线程开始
t4 线程结束
t3 线程结束
t1 线程结束
t2 线程结束
  

原因:

线程的执行顺序是不确定的。调用Thread的start()方法启动线程时,线程的执行顺序是不确定的。

也就是说,在同一个方法中,连续创建多个线程后,调用线程的start()方法的顺序并不能决定线程的执行顺序。

但是利用wait()和notify()方法后,可以保证一定的顺序。

所以在上面的结果中, t1,t2 结束一定不会都在 t3、t4 开始的上面。即:只有当调用了线程 t3 或 t4 中的notify()方法,线程 t1 和 t2 中wait()方法后的语句才会被执行。

1-2

在 main 函数中开启一个子线程,如果想让 main 主线程在子线程执行完之后才继续执行,代码该怎么写?

 
任务2 

使用 synchronized 实现抢票程序:某商场做活动,有 100 部 iPhone 可以抽奖兑换。

现在在三个柜台同时兑换,要求所有柜台已兑换的 iPhone 数量加起来刚好是 100,既不能多换,也不能少换。

要求:

用 Thread 类实现;

用 Runnable 接口实现。

Runnable 类
package com.xxm.advanced_camp.mission10_multithreading;



public class SellPhones {
    private static int iphones = 100;

    public static void main(String[] args) {


        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized ("lock") {
                        if (iphones > 0) {
                            String threadName = Thread.currentThread().getName();
                            System.out.println(threadName + "柜台正在兑换第 " + iphones + " 台 iPhone");
                            iphones--;
                        }
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized ("lock") {
                        if (iphones > 0) {
                            String threadName = Thread.currentThread().getName();
                            System.out.println(threadName + "柜台正在兑换第 " + iphones + " 台 iPhone");
                            iphones--;
                        }
                    }
                }
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized ("lock") {
                        if (iphones > 0) {
                            String threadName = Thread.currentThread().getName();
                            System.out.println(threadName + "柜台正在兑换第 " + iphones + " 台 iPhone");
                            iphones--;
                        }
                    }
                }
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}
Thread 类:

1、先实现 Thread 接口,覆写run()方法

package com.xxm.advanced_camp.mission10_multithreading.task2;

import com.xxm.advanced_camp.mission10_multithreading.task2.SellPhones2;

public class MyThread extends Thread{
    @Override
    public void run(){
        while (true) {
            synchronized ("lock") {
                if (SellPhones2.iphones > 0) {
                    String threadName = Thread.currentThread().getName();
                    System.out.println(threadName + "柜台正在兑换第 " + SellPhones2.iphones + " 台 iPhone");
                    SellPhones2.iphones--;
                }
            }
        }

    }
}

2、创建MyThread类的实例对象,直接start,完成。

package com.xxm.advanced_camp.mission10_multithreading.task2;




public class SellPhones2 {
    public static int iphones = 100;

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

任务3

使用 newScheduledThreadPool 线程池实现每隔 1 分钟打印一条消息。

答:代码如下:

package com.xxm.advanced_camp.mission10_multithreading.task3;



import java.util.concurrent.*;

public class Task3 {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        pool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("一分钟过去了");
            }
        }, 0, 1, TimeUnit.MINUTES);
    }
}

任务4

(1)在 Windows 系统中,以递归方式读取 C 盘中所有的目录和文件,并打印出每个文件的大小和每个目中文件的数量。

答:思路:

1、创建File 对象,

2、创建一个打印的方法printAll(File f)。该方法中,使用listFiles方法创建File[]数组,遍历获取f对象下的所有文件及文件夹。

3、对数组中的所有对象,进行判断:是文件,则打印其绝对路径;是文件夹,则打印其绝对路径,并让这个文件夹调用printAll方法,实现递归。

package com.xxm.advanced_camp.Mission11_IO;

import java.io.File;


public class Task1 {
    public static void main(String[] args) {
        //1、创建File对象
        File f = new File("C:");
        printAll(f);
    }

    //2、创建遍历打印的方法
    public static void printAll(File file) {

        File[] files = file.listFiles();
        for (File f : files) {
            //判断是否为文件或文件夹
            if (f.isFile()) {
                System.out.println(f.getAbsolutePath());
            } else if (f.isDirectory()) {
                System.out.println(f.getAbsolutePath());
                printAll(f);
            }
        }
    }
}package com.xxm.advanced_camp.Mission11_IO;

import java.io.File;


public class Task1 {
    public static void main(String[] args) {
        //1、创建File对象
        File f = new File("C:");
        printAll(f);
    }

    //2、创建遍历打印的方法
    public static void printAll(File file) {

        File[] files = file.listFiles();
        for (File f : files) {
            //判断是否为文件或文件夹
            if (f.isFile()) {
                System.out.println(f.getAbsolutePath());
            } else if (f.isDirectory()) {
                System.out.println(f.getAbsolutePath());
                printAll(f);
            }
        }
    }
}

任务5

在网络上下载一个大文件(任何文件都可以,可以是 exe 文件,也可以是日志、电影或其它类型的大文件),然后使用 Java 标准 I/O 模型读取并复制成另一份文件:

用字节流实现;

答:

package com.xxm.advanced_camp.Mission11_IO;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;


public class Task2 {
    public static void main(String[] args) throws Exception {
        File file = new File("E:\xxm_C1\计算机通识-1.mp4");


        //1.指定数据源
        FileInputStream fis = new FileInputStream(file);

        //2.指定输出位置
        FileOutputStream fos = new FileOutputStream("E:\xxm_C1\计算机通识的分身1号.mp4");

        //3.读取数据
        byte[] b = new byte[1024];
        int len;
        while ((len = fis.read(b)) != -1) {
            fos.write(b, 0, len);
        }

        //4.释放资源
        fis.close();
        fos.close();
    }
}

用字符流实现:

package com.xxm.advanced_camp.Mission11_IO;

import java.io.FileReader;
import java.io.FileWriter;

public class Task2_2 {
    public static void main(String[] args) throws Exception {
        //1.指定输入源
        FileReader fr = new FileReader("C:\Users\yyq\Desktop\日报月25日.md");

        //2.指定输出位置
        FileWriter fw = new FileWriter("C:\Users\yyq\Desktop\日报月25日的日报的分身.md");

        //3.读取数据
        char[] c = new char[1024];
        int len;
        while ((len = fr.read(c)) != -1) {
            fw.write(c, 0, len);
            fw.flush();
        }

        //4.释放资源
        fr.close();
        fw.close();
    }


}

任务6

针对练习 2,用 NIO 重新实现,然后比较一下执行效率。

答:

package com.xxm.advanced_camp.Mission11_IO;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;

public class Task3 {
    public static void main(String[] args) throws Exception {
        FileChannel in = new FileInputStream("C:\Users\yyq\Desktop\日报\微型 ORM 框架.md").getChannel(),
                out = new FileOutputStream("C:\Users\yyq\Desktop\日报\微型 ORM 框架的 NIO 分身.md").getChannel();
        in.transferTo(0, in.size(), out);
    }
}

复制同一个文本文件时,字符流和 NIO 执行时间差不多,但有可能是因为文本文件太小,导致产生了天花板效应。

复制同一个视频文件(1.58 G)时,字节流耗时约为 37520 毫秒;NIO 方法约为 36902 毫秒,好像差不多的样子。。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4969112.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-14
下一篇 2022-11-13

发表评论

登录后才能评论

评论列表(0条)

保存