[Java]Java 多线程编程和 IO 流

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Java]Java 多线程编程和 IO 流相关的知识,希望对你有一定的参考价值。

一、多线程

1. 进程、线程、并发、并行

  1. 进程是程序的一次执行过程,或是正在进行的一个程序。是动态过程,有其自身的产生、存在和消亡过程
  2. 线程是由进程创建的,是进程的一个实体;一个进程可以拥有多个线程
  3. 并发:同一个时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单地说,单核 CPU 实现的多任务就是并发
  4. 并行:同一个时刻,多个任务同时执行,多核 CPU 实现的多任务就是并行
// 获取系统可用处理器个数
Runtime runTime = Runtime.getRuntime();
System.out.println(runTime.availableProcessors());

2. 继承 Thread 类创建线程

当启动程序时理解为开启了一个进程,进程开启了 main 线程,在 main 线程中可以开启其它线程,只有当所有线程都消亡时,进程才结束

/**
* @author Spring-_-Bear
* @version 2021-11-21 19:53
*/
public class ThreadEx extends Thread 
   public static void main(String[] args) 
       new Dog().start();
       for (int i = 1; i <= 10; i++) 
           try 
               System.out.println(Thread.currentThread().getName() + i);
               sleep(1000);
            catch (InterruptedException e) 
               e.printStackTrace();
           
       
   


class Dog extends Thread 

   @Override
   public void run() 
       while (true) 
           try 
               System.out.println(Thread.currentThread().getName() + ":汪汪汪~~~");
               sleep(1000);
            catch (InterruptedException e) 
               e.printStackTrace();
           
       
   

3. start() 与 run()

  1. new Dog().run() 与 new Dog().start() 的区别:前者只是单纯地调用 run() 方法,而后者启用了线程,在 start() 方法中实际调用了 start0() 方法,这是个 native 修饰的方法,真正实现了启动线程
  2. start() 方法调用了 start0() 方法后,该线程并不一定会马上执行,只是将线程变成了可运行状态,具体什么时候执行,取决于 CPU

4. 实现 Runnable 接口创建线程

  1. Java 是单继承机制,若某个类已经继承了某个父类,这时只能通过实现 Runnable 接口实现线程
  2. 实现 Runnable 接口的类不能直接调用 start() 方法,可以将对象作为 Thread(Runnable) 的参数,从而调用 start() 方法,也即使用了静态代理设计模式
  3. 实现 Runnable 接口方式更加适合多个线程共享某个资源的情况,并且避免了单继承的局限
/**
* @author Spring-_-Bear
* @version 2021-11-21 20:50
*/
public class Thread02 
   public static void main(String[] args) 
       // 静态代理设计模式
       new Thread(new Cat()).start();
   


class Cat implements Runnable 
   @Override
   public void run() 
       while (true) 
           System.out.println("喵喵喵~~~");
           try 
               Thread.sleep(1000);
            catch (InterruptedException e) 
               e.printStackTrace();
           
       
   

5. 多线程售票问题

/**
* @author Spring-_-Bear
* @version 2021-11-21 21:48
*/
public class Ticket 
   public static void main(String[] args) 
       SellTicket sellTicket = new SellTicket();
       Thread thread = new Thread(sellTicket);
       Thread thread1 = new Thread(sellTicket);
       Thread thread2 = new Thread(sellTicket);
       thread.start();
       thread1.start();
       thread2.start();
   


class SellTicket implements Runnable
   private int ticketNum = 100;

   @Override
   public void run() 
       while (true) 
           if (ticketNum <= 0) 
               System.out.println("售票结束···");
               break;
           
           try 
               Thread.sleep(100);
            catch (InterruptedException e) 
               e.printStackTrace();
           
           System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票,余票:" + (--ticketNum));
       
   

6. 线程常用 API

方法名功能
setName(String)设置线程名
getName()获取线程名
start()启动线程
run()调用线程对象的 run() 方法
setPriority(int)设置线程优先级
getPriority()获得线程优先级
sleep(long)休眠线程
interrupt()中断线程,线程并未消亡

线程优先级

/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;

/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;

/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;

7. 线程插队与线程礼让

方法名功能
yield()线程礼让,让出 CPU 资源,但礼让时间不确定,是否礼让成功不确定,取决于 os
join()线程插队,一旦插队成功,则必须执行完插入线程的所有任务
/**
* @author Spring-_-Bear
* @version 2021-11-21 22:49
*/
public class JoinEx 
   public static void main(String[] args) throws InterruptedException 
       Thread temp = new Thread(new Temp());
       temp.start();

       for (int i = 1; i <= 20; i++) 
           System.out.println(Thread.currentThread().getName() + ":" + i);
           Thread.sleep(1000);
           if (i == 5) 
               temp.join();
               // main 线程礼让,不一定礼让成功
               // Thread.yield();
           
       
   


class Temp implements Runnable
   @Override
   public void run() 
       for (int i = 1; i <= 20; i++) 
           System.out.println(Thread.currentThread().getName() + ":" + i);
           try 
               Thread.sleep(1000);
            catch (InterruptedException e) 
               e.printStackTrace();
           
       
   

8. 守护线程

  1. 用户线程:也叫工作线程,当线程的任务执行完成或以通知方式结束
  2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。经典守护线程:垃圾回收机制
/**
* @author Spring-_-Bear
* @version 2021-11-22 19:10
*/
public class MyDaemon 
   public static void main(String[] args) throws InterruptedException 
       Thread thread = new Thread(new Daemon());
       // 设置为 main 线程的守护线程
       thread.setDaemon(true);
       thread.start();
       Thread.sleep(5000);
       System.out.println("妈妈回家了,小明结束写作业!");
   


class Daemon implements  Runnable
   @Override
   public void run() 
       while (true) 
           System.out.println("小明写作业中···");
           try 
               Thread.sleep(1000);
            catch (InterruptedException e) 
               e.printStackTrace();
           
       
   

9. 线程的七大状态

状态说明
NEW尚未启动的线程处于此状态。 该状态线程对象被创建,但还未调用start方法
RUNNABLE可运行线程的线程状态,细分为 Running 和 Ready 两个状态。处于可运行状态的线程可能正在 Java 虚拟机中执行,也可能正在等待来自操作系统的其他资源
BLOCKED被阻塞等待监视器锁的线程处于此状态。处于该状态的线程正在等待获取一个监视器锁进入同步代码或方法;也可能是在调用了Object.wait后等待一个监视器锁重新进入同步代码或方法
WAITING线程处于等待状态;处于该状态的线程可能是因为调用了Object.wait()、Thread.join()、LockSupport.park() 中的某一个方法;处于该状态的线程正在等待其他线程完成一些特定的操作
TIMED_WAITING正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。线程处于定时等待状态,这个等待是有具有指定时间的;处于这个状态的线程可能是调用了具有指定时间的 Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()、LockSupport.parkUntil()
TERMINATED已退出的线程处于此状态

10. 线程同步机制

  1. 线程同步:当有一个线程在对内存进行操作时,其它线程都不可以对这个内存进行操作,直到该线程完成操作,其它线程才能对该内存进行操作,也即同一时刻只允许一个线程操作
  2. 得到对象的锁,才可以操作对象的代码;也可以将 synchronized 加在方法声明中,表示整个方法为同步方法
  3. Java 语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应一个可称为 “互斥锁” 的标记,这个标记用来保证在任意时刻,有且仅有一个线程可以访问该对象。用关键字 synchronized 来与对象的互斥锁联系
  4. 同步的局限性:程序的执行效率降低
  5. 非静态同步方法的锁可以是它本身 this,也可以是其它对象(要求是同一个对象)
  6. 静态同步方法的锁是当前类本身(ClassName.class)
/**
* @author Spring-_-Bear
* @version 2021-11-21 21:48
*/
public class Ticket 
   public static void main(String[] args) 
       SellTicket sellTicket = new SellTicket();
       Thread thread1 = new Thread(sellTicket);
       Thread thread2 = new Thread(sellTicket);
       Thread thread3 = new Thread(sellTicket);
       thread1.start();
       thread2.start();
       thread3.start();
   


class SellTicket implements Runnable 
   private int ticketNum = 100;
   Object object = new Object();
   
   @Override
   public void run() 
       sell();
   

   public void sell() 
       while (true) 
           synchronized (this) 
               if (ticketNum <= 0) 
                   System.out.println("售票结束···");
                   break;
               
               System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票,余票:" + (--ticketNum));
               try 
                   Thread.sleep(100);
                catch (InterruptedException e) 
                   e.printStackTrace();
               
           
       
   


/*
* 由于主方法中只创建了一个 SellTicket 对象,所以线程 0、1、2 操作的都是同一个对象
* 因而将锁加在对象本身等价于将锁将在此对象的其它对象(字段),也即下面的两段代码等价
* synchronized (this) <=> synchronized (object)
*/
  1. 静态方法中的互斥锁加在类上
class SellTicket implements Runnable 
   public static void getTicket() 
       synchronized (SellTicket.class) 
           System.out.println("售出一张票");
       
   

11. 线程死锁

/**
* @author Spring-_-Bear
* @version 2021-11-22 21:02
*/
public class DeadLockDemo 
   public static void main(String[] args) 
       new DeadLock(true).start();
       new DeadLock(false).start();
   


class DeadLock extends Thread 
   static Object object1 = new Object();
   static Object object2 = new Object();
   boolean flag;

   public DeadLock(boolean flag) 
       this.flag = flag;
   

   @Override
   public void run() 
       if (flag) 
           synchronized (object1) 
               System.out.println(Thread.currentThread().getName() + " 获得对象 1 的锁,尝试获取对象 2 的锁···");
               synchronized (object2) 
                   System.out.println(Thread.currentThread().getName() + " 成功获得对象 2 的锁!");
               
           
        else 
           synchronized (object2) 
               System.out.println(Thread.currentThread().getName() + " 成功获得对象 2 的锁,尝试获取对象 1 的锁···");
               synchronized (object1) 
                   System.out.println(Thread.currentThread().getName() + " 成功获得对象 1 的锁!");
               
           
       

   

12. 释放锁

  1. 当前线程的同步方法、同步代码块执行结束会释放锁
  2. 当前线程在同步方法、同步代码块中遇到 break、return 会释放锁
  3. 当前线程在同步方法、同步代码块中出现了未处理的 Error 或 Exception 导致结束会释放锁
  4. 当前线程在同步方法、同步代码块中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁
  5. 当前线程在执行同步代码块、同步方法的过程中调用了 Thread.sleep()、Thread.yield() 方法暂停当前线程的执行,不会释放锁
  6. 线程执行同步代码块时,其它线程调用了该线程的 suspend() 方法将该方法挂起,该线程不会释放锁。suspend()、resume() 方法均已过时,不再推荐使用

二、IO流

1. File 类常用 API

  1. 流:数据在数据源(文件)和程序(内存)之间经历的路径
  2. 以程序(内存)为参照,流入内存为输入流,流出内存为输出流
方法名功能
new File(String)根据路径构建一个 File 对象
new File(File,String)根据父目录文件 + 子路径构建
new File(String,String)根据父目录 + 子路径构建
getName()获取文件名,包含文件扩展名
getAbsolutePath()获取文件绝对路劲
getParent()获取文件父级目录
length()获取文件大小,以字节为单位
exists()判断是否存在
isFile()判断是否是文件
isDirectory()判断是否是目录
mkdir()创建一级目录
mkdirs()创建多级目录
delete()删除空目录或文件

2. IO 流原理和分类

  1. 按操作数据单位不同分为字节流和字符流
  2. 按数据流的流向不同分为输入流和输出流
  3. 按流的角色的不同分为字节流和处理流(包装流)
  4. 抽象基类:InputStream(字节输入流)、 OutputStream(字节输出流)、Reader(字符输入流)、 Writer(字符输出流)

3. 文件拷贝

String src = "C:\\\\Users\\\\Admin\\\\Desktop\\\\BeFree.jpg";
String dst = "C:\\\\Users\\\\Admin\\\\Desktop\\\\BeFree.png";
FileInputStream fileInputStream = new FileInputStream(src);
// 无需追加写入,因为并未再次打开文件
FileOutputStream fileOutputStream = new FileOutputStream(dst);
int readLen;
byte[] buf = new byte[1024];
while ((readLen = fileInputStream.read(buf)) != -1) 
   fileOutputStream.write(buf, 0, readLen);

fileInputStream.close();
fileOutputStream.close();

4. 字符流

  1. FileWriter 使用后,必须 close() 或 flush(),否则数据只是写在缓冲区,未写入文件
  2. 字符流底层调用字节流实现具体功能

5. 节点流与处理流

  1. 节点流可以从一个特定的数据源读写数据,如 FileReader、FileWriter
  2. 处理流(包装流)是 “连接” 在已存在的流,为程序提供了更为强大的读写功能,如 BufferedReader、BufferedWriter
  3. 节点流是底层流,直接对数据源进行操作
  4. 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更加方便的方法来完成文件操作,底层调用节点流实现具体功能。优点主要有以下两点:性能的提高:主要以增加缓冲的方式来提高输入输出的效率、操作的便捷:提供了一系列便捷的方法来一次输入、输出大批量的数据(比如 BufferedReader 可以一次读取一行,而 FileReader 最多以字符数组形式进行读取)
  5. 使用处理流时,只需关闭外层流即可,对应的节点流会自动关闭

处理流设计理念体现了修饰器模式。列表中前 5 行为节点流,其余行对应的流为处理流

以上是关于[Java]Java 多线程编程和 IO 流的主要内容,如果未能解决你的问题,请参考以下文章

(c)2006-2024 SYSTEM All Rights Reserved IT常识

分类字节输入流字节输出流字符输入流字符输出流
1抽象基类InputStreamOutputStreamReaderWriter
2访问文件FileInputStreamFileOutputStreamFileReaderFileWriter
3访问数组ByteArrayInputSteamByteArrayOutputStramCharArrayReaderCharArrayWriter
4访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
5访问字符串StringReaderStringWriter
缓冲流BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
转换流InputStreamReader