IO 流

概述

1、IO流用来处理设备之间的数据传输
上传文件和下载文件
2、Java对数据的操作是通过流的方式
3、Java用于操作流的对象都在IO包中

I
   input    从硬盘读取数据到内存    read 读
   
O
   output    从内存写入数据到硬盘    write 写

分类

按照数据流向
    输入流    读入数据    字节输入流、字符输入流
    输出流    写出数据    字节输出流、字符输出流
    
按照数据类型
    字节流                字节输入流、字节输出流
    字符流                字符输入流、字符输出流

class InputStream 字节输入流基类
--| class FileInputStream 文件操作字节输入流

class OutputStream 字节输出流基类
--| class FileOutputStream 文件操作字节输出流

class Reader 字符输入流基类
--| class FileReader 文件操作字符输入流

class Writer 字符输出流基类
--| class FileWriter 文件操作字符输出流
    
缓冲流:
    BufferedInputStream    
        字节输入缓冲流
    BufferedOutputStream
        字节输出缓冲流    
    BufferedReader    
        字符输入缓冲流
    BufferedWriter    
        字符输出缓冲流

FileInputStream 文件操作字节输入流

Constructor构造方法

// 根据用户指定的文件路径创建对应的FileInputStream,文件操作输入字节流,如果文件不存在,抛出异常FileNotFoundException
FileInputStream(String filePath);
        
// 根据用户指定对应文件的File类对象,创建对应的FileInputStream,如果文件不存在,抛出异常FileNotFoundException            
FileInputStream(File file);

Method成员方法

// 从文件中读取一个字节数据返回。如果读取到底末尾,返回-1 EOF End Of File
int read();
        
// 从文件中读取数据到缓冲数组buf中,返回值类型是从文件中读取到的字节个数,如果读取到文件末尾,返回-1, EOF End Of File 。如果在运行过程中出现了问题,抛出异常IOException       
int read(byte[] buf); 【重点,效率高】

操作流程

1. 明确对应文件的路径,可以选择直接给予对应的String类型路径,或者创建对应的File类对象,作为参数
2. 创建FileInputStream文件操作字节输入流对象,打开文件操作管道
3. 从FileInputStream对象中使用方法,读取数据
4. 关闭资源!!!FileInputStream类对象 ==> 水龙头!!!

案例代码

public class Test1 {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(new File("C:/Users/CJF/Desktop/Test.txt"));

        int content = fileInputStream.read();

        System.out.println((char) content);

//        byte[] buf = new byte[1024 * 16];

        while (-1 != (content = fileInputStream.read())) {
            System.out.print((char)content);
        }

        fileInputStream.close();
    }
}

案例代码二

public class Test2 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream(new File("C:/Users/CJF/Desktop/Test.txt"));

        byte[] buf = new byte[1024 * 16];

        int count = -1;
        
        while (-1 != (count = fis.read(buf))) {
            System.out.println(new String(buf, 0, count));
        }
        
        fis.close();
    }
}

【注意】一般都会使用缓冲数组,因为单个字节读取效率太低了

FileOutputStream 文件操作字节输出流

Constructor构造方法

// 根据用户指定的路径,创建对应的FileOutputStream文件操作输出流对象。如果路径不合法,抛出异常FileNotFoundException。采用写入数据到文件的方式,是【删除写】!!!文件内容清空,再写入数据
FileOutputStream(String filePath);

// 根据用户指定的File类对象,创建对应FileOutputStream文件操作输出流对象,如果路径不合法,抛出异常FileNotFoundException。采用写入数据到文件的方式,是【删除写】!!!文件内容清空,再写入数据
FileOutputStream(File file);
    
// 根据用户指定的路径,创建对应的FileOutputStream文件操作输出流对象。如果路径不合法,抛出异常FileNotFoundException。
// append参数是boolean类型,如果传入参数为true,表示【追加写】,在文件末尾写入数据
FileOutputStream(String filePath, boolean append);
        
// 根据用户指定的File类对象,创建对应FileOutputStream文件操作输出流对象,如果路径不合法,抛出异常FileNotFoundException。
// append参数是boolean类型,如果传入参数为true,表示【追加写】,在文件末尾写入数据
FileOutputStream(File file, boolean append);    

Method成员方法

// 写入一个字节数据写入到文件中
void write(int b);
    
// 写入一个字节数组到文件中        
void write(byte[] buf);
    
// 写入一个字节数组到文件中,要求从off偏移位置开始,计数count     
void write(byte[] buf, int off, int count);    

操作流程

1. 明确对应文件的路径,可以选择直接给予对应的String类型路径,或者创建对应的File类对象,作为参数
2. 创建FileOutputStream文件操作输出字节流对象,打开文件操作管道
3. 使用FileOutputStream对象写入数据到文件中
4. 关闭资源!!!

【注意】
    1. FileOutputStream拥有创建文件的能力,在路径合法,且对应目录有写入权限下可以创建文件
    2. 区分删除写和追加写

案例代码

public class Test {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream(new File("C:/Users/CJF/Desktop/Test.txt"));

        fos.write(97);
        System.out.println();

        FileOutputStream fos2 = new FileOutputStream(new File("C:/Users/CJF/Desktop/Test.txt"), true);

        fos2.write("\n今天是个好天气".getBytes());
        
        fos2.close();
        fos.close();
    }
}

FileReader 文件操作字符输入流

Constructor构造方法

// 根据指定路径的文件创建对应的文件字符输入流对象,如果文件不存在,抛出异常FileNotFoundException
FileReader(String filePath);    
        
// 根据指定路径的File类对象创建文件字符输入流对象,如果文件不存在,抛出异常FileNotFoundException
FileReader(File file);        

Method成员方法

// 从文件中读取一个字符数据,返回值为int类型,int类型数据中有且只有低十六位是有效数据,如果读取到文件末尾返回-1 EOF End Of File
int read();
        
// 从文件中读取数据到char类型缓冲数组buf,返回值是读取到字符个数。如果读取到文件末尾返回-1 EOF End Of File
int read(char[] buf);    

操作流程

1. 明确需要读取数据的文件
2. 创建FileReader对象,打开文件操作管道
3. 使用FileReader类对象方法,读取文件数据
4. 关闭资源

案例代码

public class Test {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader(new File("C:/Users/CJF/Desktop/Test.txt"));

        char[] buf = new char[1024 * 16];

        int content = -1;

        while (-1 != (content = fr.read(buf))) {
            System.out.println(new String(buf, 0, content));
        }
        
        fr.close();
    }
}

FileWriter 文件操作字符输出流

Constructor构造方法

// 根据用户指定的路径,创建对应的FileWriter文件操作字符输出流对象。如果路径不合法,抛出异常FileNotFoundException。采用写入数据到文件的方式,是【删除写】!!!文件内容清空,在写入数据
FileWriter( String filePath);
    
// 根据用户指定的File类对象,创建对应FileWriter文件操作字符输出流对象,如果路径不合法,抛出异常FileNotFoundException。采用写入数据到文件的方式,是【删除写】!!!文件内容清空,在写入数据
FileWriter(File file);
    
// 根据用户指定的路径,创建对应的FileWriter文件操作字符输出流对象。如果路径不合法,抛出异常FileNotFoundException。append参数是boolean类型,如果传入参数为true,表示【追加写】,在文件末尾写入数据    
FileWriter(String filePath, boolean append);
    
// 根据用户指定的File类对象,创建对应FileWriter文件操作字符输出流对象,如果路径不合法,抛出异常FileNotFoundException。append参数是boolean类型,如果传入参数为true,表示【追加写】,在文件末尾写入数据    
FileWriter(File file, boolean append);    

Method成员方法

// 写入一个字符数据写入到文件中
void write(int ch);
    
// 写入一个字符数组到文件中
void write(char[] buf);
    
// 写入一个字符数组到文件中,要求从off偏移位置开始,计数count
void write(char[] buf, int off, int count);    
    
// 写入一个字符串到文件中
void write(String str);
    
// 写入一个字符串到文件中,要求从offset偏移位置开始,计数count
void write(String str, int offset, int count);
    
        
【注意】
    1. FileWriter拥有创建文件的能力,在路径合法,且对应目录有写入权限下可以创建文件
    2. 区分删除写和追加写

案例代码

public class Test {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter(new File("C:/Users/CJF/Desktop/Test.txt"), true);

        fw.write("\n今天是520情人节");

        fw.close();
    }
}

复制文件

public class Demo8 {
    public static void main(String[] args) throws IOException {
        // 明确操作源文件
        File src = new File("D:/EclipseWorkSpace/FC2020/src/com/fc/z/io/a.txt");
        
        // 明确操作源文件
        File dest = new File("D:/EclipseWorkSpace/FC2020/src/com/fc/z/io/b.txt");

        // 创建读对象
        FileInputStream fis = new FileInputStream(src);
        
        // 创建写对象
        FileOutputStream fos = new FileOutputStream(dest);
        
        // 缓存
        byte[] buf = new byte[1024 * 16];
        
        int content = -1;
        
        // 读取数据并写入
        while (-1 != (content = fis.read(buf))) {
            fos.write(buf, 0, content);
        }

        // 关闭资源
        fos.close();
        fis.close();
    }
}

总结

1、流程是一样的
   明确文件
   打开管道
   操作文件
   关闭资源

2、核心方法
   read 读取,输入
   write 写入,输出

3、输出流有创建文件的能力。

4、 输出流需要注意是删除写还是追加写。

5、输入流有缓冲比没有缓冲效率高很多

6、一定要注意关闭资源!!!resource

7、一般还是用字节流,避免文件损坏

缓冲流

概述

1、缓冲流是Java中提供的系统缓冲,底层都是一个缓冲数组,根据处理的数据方式不同,提供的数据有字节缓冲数组和字符缓冲数组。

2、字节缓冲流,默认的字节数组缓冲区是8KB 
   byte[] buffer = new byte[1024 * 8];

3、字符缓冲流,默认的字符数组缓冲区是16KB
   char[] buffer = new char[1024 * 8];

4、【重点】
   任何一个缓冲流都没有任何操作文件的能力!!!读取文件数据,写入文件数据,都是依赖于对应的字符流或者字节流提供的!!!

5、缓冲流使用的方法,也是read write 而且是对应创建当前缓冲流使用的字符流或者字节流的!!!

6、缓冲流减少了CPU通过内存访问磁盘或者说文件的次数。极大的提高了开发效率。IO流操作文件内容都是在缓冲流内部的缓冲数组中,也就是内存中。

分类

BufferedInputStream
   字节缓冲输入流

BufferedOutputStream
   字节缓冲输出流

BufferedReader
   字符缓冲输入流

BufferedWriter
   字符缓冲输出流

BufferedInputStream 字节缓冲输入流

构造方法 Constructor

BufferedInputStream(InputStream in);
    这里需要的参数是字节输入流对象

成员方法 Method

int read();
int read(byte[] buf);
其实就是InputStream中使用的方法

案例代码

public class Test {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(
                new FileInputStream(new File("C:/Users/CJF/Desktop/Test.txt")));

        byte[] buf = new byte[1024 * 8];

        int count = -1;

        while (-1 != (count = bis.read(buf))) {
            System.out.println(new String(buf, 0, count));
        }

        bis.close();
    }
}

BufferedOutputStream 字节缓冲输出流

构造方法 Constructor

BufferedOutputStream(OutputStream out);
    这里需要一个字节输出流作为方法的参数

常用方法 Method

void write(int b);
void write(byte[] buf);
void write(byte[] buf, int off, int len);

以上方法都是OutputStream提供的方法。
    
所有的数据都是首先都是写入保存到BufferedOutputStream 底层操作的数组中,当数组填满以后,或者执行指定的方法,才会将数据之间写入到内存中。

案例代码

public class Test {
    public static void main(String[] args) throws IOException {
        BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream(new File("C:/Users/CJF/Desktop/Test.txt"), true));

        bos.write("\n今天是个好天气".getBytes());

        bos.close();
    }
}

效率总结

1、使用缓冲时间效率是远远高于未使用缓冲情况,这里是一个非常经典的空间换时间概念
    缓冲占用内存 16KB 非缓冲 4byte 时间效率大于250倍 空间占用4000倍

2、利用代码可以发现,非缓冲IO操作时使用数组作为缓冲区和使用缓冲流操作,时间效率相似。这里还是推荐使用系统提供的缓冲流,更加安全,并且提供了一些其他方法,可以作为一定参考和使用。

BufferedReader 字符输入缓冲流

构造方法 Constructor

BufferedReader(Reader in);

常用方法 Method

int read();
int read(byte[] buf);
String readLine(); 【新方法】
    从文件中读取一行数据,返回值类型是字符串,如果读取到文件默认,返回null
    一行数据??? 结尾标记 \r\n

案例代码

public class Test {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(new File("C:/Users/CJF/Desktop/Test.txt")));

        char[] buf = new char[1024 * 8];

        int count = -1;
        while (-1 != (count = br.read(buf))) {
            System.out.println(new String(buf, 0, count));
        }

        String content = null;
        while (null != (content = br.readLine())) {
            System.out.println(content);    // 这里不会再输出,因为上面已经读到文件末尾,即 null == content
        }

        br.close();
    }
}

BufferedWriter 字符输出缓冲流

构造方法 Constructor

BufferedWriter(Writer in);

常用方法

void write(int ch);
void write(char[] buf);    
void write(char[] buf, int off, int len);    
void write(String str);    
void write(String str, int off, int len);    
void newLine(); 
    换行写操作

案例代码

public class Test {
    public static void main(String[] args) throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter(new File("C:/Users/CJF/Desktop/Test.txt"), true));

        bw.write("\n今天又是个好天气");

        bw.newLine();

        bw.close();
    }
}

多线程

进程和线程

进程

概述:

​ 正在运行的程序,是系统进行资源分配和调用的独立单位

​ 每一个进程都有它自己的内存空间和系统资源

例如:

​ 打开任务管理器,可以看到电脑中执行的每一个程序,每一个程序就是一个进程

特点:

​ 1、独立性

​ 2、动态性

​ 3、并发性

线程

概述:

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

​ 一个进程如果只有一条执行路径,则称为单线程程序

​ 一个进程如果有多条执行路径,则称为多线程程序

例如:

​ 电脑管家就是一个程序 => 进程

​ 电脑管家可以同时 病毒查杀,垃圾清理,一键加速等功能

​ 这些每个功能都可以看做是线程,它们是同时进行的

特点:

​ 1、线程是CPU的最小调度单位,CPU可以很快的在多个线程间实现切换。

​ 2、运行时的线程,随时都可以被CPU给挂起。

​ 3、线程的抢占发生在任意时期

进程与线程的区别

1、一个进程可以有多个线程。但是必须要有一个主线程

2、进程间不能共享资源,但是线程间可以共享资源。

3、Java程序中,最少要有两个线程

​ 1、main线程

​ 2、JVM 的 GC 机制,守护线程

并发和并行

并发:两个或者两个以上的事务在同一个时间段发生

并行:两个或者两个以上的事务在同一个时刻发生

线程的使用

Thread类

Java中的一个线程类,同时提供了很多线程的操作使用的方法,我们想要操作线程,必须通过Thread类对象去完成。Thread 类实现了 Runnable 接口,其中的 run 方法中就是我们要被运行的代码。可以通过重写 run 方法,并调用线程的 start 使其运行,以达到我们想要的效果

自定义线程三种方式

1、自定义线程类,继承自 Thread,并重写 run 方法

2、自定义线程类,传入一个实现了 Runnable 接口的参数【重点】

3、自定义线程类,传入一个实现了 Callable 接口的参数

继承 Thread 类并重写 run 方法

案例代码

public class Demo1 {
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        
        thread1.start();
        
        System.out.println("main线程");
    }
}

class Thread1 extends Thread {
    @Override
    public void run() {
        System.out.println("测试线程");
    }
}

实现 Runnable 接口

public class Demo2 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Thread2());
        
        thread.start();
        
    }
}

class Thread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("测试线程");
    }
}

public class Demo2 {
    public static void main(String[] args) {
        // 使用匿名内部类和匿名对象
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("测试线程");
            }
        }).start();    
    }
}

实现 Callable 接口

可以有返回值

可以抛出异常

public class Test3 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<Integer> cal = new Callable<Integer>() {
            int count = 0;

            @Override
            public Integer call() throws Exception {
                count++;

                System.out.println(" Test :" + count);    //  Test :1

                return count;
            }
        };

        FutureTask<Integer> future = new FutureTask<Integer>(cal);

        Thread thread = new Thread(future);

        thread.start();

        System.out.println("返回值为:" + future.get());    // 返回值为:1
    }
}

三种创建方式的区别

继承 Thread 类:
    编写简单、单继承,所以这种类无法再继承其他类、无法实现多个线程的资源共享、扩展性无
    
实现 Runnable 接口:
    编写复杂一点,接口可以多实现,可以实现多个线程的资源共享  推荐使用
    
实现 Callable 接口:
    编码复杂,可以实现线程执行完之后进行值的返回

【重点】开发中,需要线程返回值,就使用 Callable,不需要返回值的就可以 Runnable ,最常用的是实现 Runnable

线程调度

分时调度模型

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

抢占式调度模型

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

Java 采用的是抢占式调度模型

优先级

线程的优先级就是线程获得CPU的概率,优先级越高,获取CPU的概率越大

Java中共有10种优先级,从小到大,1-10之间。10是优先级最高,默认的优先级是5

优先级方法

【注意】即便我们设置了优先级,也只是增加抢占的概率,线程并不一定会严格按照优先级执行

// 获取当前线程的优先级
public final int getPriority()
    
// 设置当前线程的优先级
public final void setPriority(int newPriority)

案例代码

public class Demo4 {
    public static void main(String[] args) {
        // 创建两个Runnable接口实现类
        Runnable run1 = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("烤羊排");
                }
            }
        };

        Runnable run2 = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("大盘鸡");
                }
            }
        };

        // 创建两个线程
        Thread thread1 = new Thread(run1);
        Thread thread2 = new Thread(run2);

        // 查看优先级
        System.out.println(thread1.getPriority());
        System.out.println(thread2.getPriority());

        // 设置优先级
        thread1.setPriority(10);
        thread2.setPriority(1);

        thread2.start();
        thread1.start();

        // 主线程
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程");
        }
    }
}

【注意】设置优先级需要在 start 方法之前设置

线程状态(生命周期)

简单理解生命周期

线程有五大状态,分别是新建、就绪、运行、阻塞、销毁

新建:

当我们实例化线程对象的时候,线程就是新建状态

就绪:

当我们调用线程的start方法之后,线程就会进入就绪状态

处于该状态的线程,随时都可以获取CPU调度

运行:

线程获取CPU的调度之后,线程抢到了时间片,可以用来运行自己任务

阻塞:

当线程因为资源竞争,或主动方法调用,让线程进入到阻塞。

常见:sleep、wait、join等等

销毁:

当线程的run方法执行结束之后,就会进入到销毁状态

【注意】程序的结束就是指的内部的所有线程全部进入到了销毁状态

扩展:六种线程状态

如果按照 java.lang.Thread.State 枚举方式,一共提供了6种线程状态

状态导致状态的发生条件
NEW(新建)线程刚刚被创建,没有启动,没有调用start方法
RUNNABLE(可运行)线程已经可以在JVM中运行,但是是否运行不确定,看当前线程是否拥有CPU执行权
BLOCKED(锁阻塞)当前线程进入一个同步代码需要获取对应的锁对象,但是发现当前锁对象被其他线程持有,当前线程会进入一个BLOCKED。如果占用锁对象的线程打开锁对象,当前线程持有对应锁对象,进入Runnable状态
WAITING(无限等待)通过一个wait方法线程进入一个无限等待状态,这里需要另外一个线程进行唤醒操作。进入无限等待状态的线程是无法自己回到Runnable状态,需要其他线程通过notify或者notifyAll方法进行唤醒操作
TIMED_WAITING(计时等待)当前线程的确是等待状态,但是会在一定时间之后自动回到Runnable状态,例如 Thread.sleep() 或者是Object类内的wait(int ms);
TERMINATED(被终止)因为Run方法运行结束正常退出线程,或者说在运行的过程中因为出现异常导致当前线程被销毁
TIMED_WAITING(计时等待)
// 在对应线程代码块中,当前线程休眠指定的时间
Thread.sleep(int ms);

    sleep方法
        1. 调用之后休眠指定时间
        2. sleep方法必须执行在run方法内,才可以休眠线程
        3. sleep不会打卡当前线程占用的锁对象。
    
// 让当前线程进入一个计时等待状态        
void wait(long timeout);
    Object类内 
        1. 规定的时间及时完毕,线程回到可运行状态
        2. 在等待时间内,通过其他线程被notify或者notifyAll唤醒

BLOCKED(锁阻塞)
线程中有锁存在,线程需要进入带有锁操作的同步代码,如果锁对象被别人持有,只能在锁外等待

锁阻塞状态的线程是否能够抢到锁对象有很多因素
    1. 优先级问题,非决定因素
    2. CPU执行概率问题。

后期高并发一定会存在多线程操作锁对象问题,秒杀,抢购...
    队列方式来处理

线程状态 WAITING(无限等待)
当某一个线程被执行wait()方法,需要等待另外的一个线程进行唤醒操作。


public void wait();
    在哪一个线程中执行,就会让当前线程进入一个无限等待状态。
            1. 所在线程进入无限等待状态
            2. 开启【锁对象】
    
public void notify();
    唤醒和当前锁对象有关的无限等待线程中的一个,随机选择。
            1. 唤醒一个无限等待状态线程
            2. 开启【锁对象】
    
public void notifyAll();
    唤醒所有和当前锁对象有关的无限等待线程
            1. 唤醒所有线程
            2. 开启【锁对象】
            3. 线程进入锁对象抢占过程,就有可能进入一个锁阻塞状态。

线程执行的所有状态分析图

守护线程

线程分为:用户线程和守护线程,Java中默认创建的线程就是用户线程

特点

当守护的用户线程销毁的时候,守护线程也会跟着消亡。无论守护线程是否执行结束都会随着用户线程一起销毁

作用

1、自动下载

2、操作日志

3、操作监控

方法

// 判断该线程是否为守护线程
boolean isDaemon();

// 当传入 true 时,将当前线程设置为守护线程:
void setDaemon(boolean on)

案例代码

public class Demo5 {
    public static void main(String[] args) {

        // 创建两个线程
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("大盘鸡");
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("烤羊排" + i);
                }
            }
        });

        // 查看是否是守护线程
        System.out.println(thread2.isDaemon());

        // 设置守护线程
        thread2.setDaemon(true);

        thread1.start();
        thread2.start();

        // 主线程
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程");
        }
    }
}

【注意】主线程和 GC(garbage collection 垃圾回收机制) 线程就是一对用户线程与守护线程,GC 守护主线程

线程控制

方法

// 线程休眠(运行-->阻塞):
static void sleep(long millis);

// 线程加入(运行-->阻塞):
void join();
    
// 线程礼让(运行-->就绪):    
void yield();

sleep()

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

public class Demo6 {
    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("大盘鸡" + i);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        thread.start();

        // 主线程
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程");
        }
    }
}

【注意】使用sleep方法需要对其进行异常捕获

join()

在当前线程中,执行另一个线程的join方法,然后当前线程就会阻塞,等待插入的线程执行完毕之后,才会从阻塞状态进入到就绪状态,重新参与CPU抢夺!

就绪状态的线程的抢占发生在任意时期

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("烤羊排" + i);
                }
            }
        });
        
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    
                    System.out.println("大盘鸡" + i);
                }
            }
        });

        thread2.start();
        // 线程加入
        thread2.join();
        
        thread1.start();
        
        // 主线程
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程");
        }
    }
}

yidle()

可以让当前正在运行的线程暂停,并执行其他线程。但是不会让当前线程阻塞,而且让当前的线程进入到就绪状态。

实际上:线程执行yield之后,只有比当前线程的优先级更高或者相同的才有机会参与抢夺CPU,而且当前线程也会参与抢夺

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("烤羊排" + i);
                    
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("大盘鸡" + i);
                    
                    if (i == 5) {
                        System.out.println("线程礼让");
                        Thread.yield();
                    }
                }
            }
        });
        
        thread1.setPriority(9);
        thread2.setPriority(1);

        thread2.start();
        
        thread1.start();
        
        // 主线程
        System.out.println("main线程");
    }
}

线程相关方法

static Thread currentThread();
    返回对当前正在执行的线程对象的引用

long getId();
    返回该线程的标识符。线程 ID 是一个正的 long 数,在创建该线程时生成。线程 ID 是唯一的,并终生不变。线程终止时,该线程 ID 可以被重新使用 

String getName();
    返回该线程的名称
    
void setName(String name);
    改变线程名称,使之与参数 name 相同
        
Thread.State getState();
    返回该线程的状态
    
boolean isAlive();
    测试线程是否处于活动状态

boolean isInterrupted();
    测试线程是否已经中断
    
void interrupt();
    中断线程

static boolean interrupted();
    判断当前线程是否已经中断。线程的中断状态由该方法清除。

案例代码

public class Demo9 {
    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("烤羊排" + i);
                    
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("大盘鸡" + i);
                    
                    if (i == 5) {
                        System.out.println("线程礼让");
                        Thread.yield();
                    }
                }
            }
        });

//        thread2.start();
        
        thread1.start();
        
        System.out.println("获取当前线程:" + Thread.currentThread());
        
        System.out.println("获取线程1的标识:" + thread1.getId());
        System.out.println("获取线程2的标识:" + thread2.getId());
        System.out.println("获取当前线程的标识:" + Thread.currentThread().getId());
        
        System.out.println("获取当前线程的名称:" + thread1.getName());
        
        // 设置线程的名称
        thread1.setName("线程1");
        
        System.out.println("获取当前线程的名称:" + thread1.getName());

        System.out.println("获取当前线程的状态:" + thread1.getState());
        
        System.out.println("获取当前线程是否存活:" + thread1.isAlive());

        System.out.println("获取当前线程是否中断:" + thread1.isInterrupted());
        
        // 中断线程
        thread1.interrupt();
        
        System.out.println("获取当前线程是否中断:" + thread1.isInterrupted());
        
        System.out.println("获取当前线程是否中断:" + Thread.interrupted());
        
        // 主线程
        System.out.println("main线程");
    }
}
最后修改:2021 年 01 月 24 日 12 : 19 PM
如果觉得此文章有用,请随意打赏