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

学习内容

多线程

进程

在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和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实现类的实例,并以此实例作为Threadtarget来创建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;

/*
开启四个线程,两个线程调用锁的 wait 方法,另外两个调用锁的 notify 方法,观察执行结果并解释原因。
 */
public class Task1 {
   
    public static void main(String[] args) {
   
        Thread t1 = new Thread(new Runnable() {
   
            @Override
            public void run() {
   
                synchronize
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值