[Java]Java 多线程编程和 IO 流
Posted Spring-_-Bear
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Java]Java 多线程编程和 IO 流相关的知识,希望对你有一定的参考价值。
一、多线程
1. 进程、线程、并发、并行
- 进程是程序的一次执行过程,或是正在进行的一个程序。是动态过程,有其自身的产生、存在和消亡过程
- 线程是由进程创建的,是进程的一个实体;一个进程可以拥有多个线程
- 并发:同一个时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单地说,单核 CPU 实现的多任务就是并发
- 并行:同一个时刻,多个任务同时执行,多核 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()
- new Dog().run() 与 new Dog().start() 的区别:前者只是单纯地调用 run() 方法,而后者启用了线程,在 start() 方法中实际调用了 start0() 方法,这是个 native 修饰的方法,真正实现了启动线程
- start() 方法调用了 start0() 方法后,该线程并不一定会马上执行,只是将线程变成了可运行状态,具体什么时候执行,取决于 CPU
4. 实现 Runnable 接口创建线程
- Java 是单继承机制,若某个类已经继承了某个父类,这时只能通过实现 Runnable 接口实现线程
- 实现 Runnable 接口的类不能直接调用 start() 方法,可以将对象作为 Thread(Runnable) 的参数,从而调用 start() 方法,也即使用了静态代理设计模式
- 实现 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. 守护线程
- 用户线程:也叫工作线程,当线程的任务执行完成或以通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。经典守护线程:垃圾回收机制
/**
* @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. 线程同步机制
- 线程同步:当有一个线程在对内存进行操作时,其它线程都不可以对这个内存进行操作,直到该线程完成操作,其它线程才能对该内存进行操作,也即同一时刻只允许一个线程操作
- 得到对象的锁,才可以操作对象的代码;也可以将 synchronized 加在方法声明中,表示整个方法为同步方法
- Java 语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应一个可称为 “互斥锁” 的标记,这个标记用来保证在任意时刻,有且仅有一个线程可以访问该对象。用关键字 synchronized 来与对象的互斥锁联系
- 同步的局限性:程序的执行效率降低
- 非静态同步方法的锁可以是它本身 this,也可以是其它对象(要求是同一个对象)
- 静态同步方法的锁是当前类本身(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)
*/
- 静态方法中的互斥锁加在类上
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. 释放锁
- 当前线程的同步方法、同步代码块执行结束会释放锁
- 当前线程在同步方法、同步代码块中遇到 break、return 会释放锁
- 当前线程在同步方法、同步代码块中出现了未处理的 Error 或 Exception 导致结束会释放锁
- 当前线程在同步方法、同步代码块中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁
- 当前线程在执行同步代码块、同步方法的过程中调用了 Thread.sleep()、Thread.yield() 方法暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其它线程调用了该线程的 suspend() 方法将该方法挂起,该线程不会释放锁。suspend()、resume() 方法均已过时,不再推荐使用
二、IO流
1. File 类常用 API
- 流:数据在数据源(文件)和程序(内存)之间经历的路径
- 以程序(内存)为参照,流入内存为输入流,流出内存为输出流
方法名 | 功能 |
---|---|
new File(String) | 根据路径构建一个 File 对象 |
new File(File,String) | 根据父目录文件 + 子路径构建 |
new File(String,String) | 根据父目录 + 子路径构建 |
getName() | 获取文件名,包含文件扩展名 |
getAbsolutePath() | 获取文件绝对路劲 |
getParent() | 获取文件父级目录 |
length() | 获取文件大小,以字节为单位 |
exists() | 判断是否存在 |
isFile() | 判断是否是文件 |
isDirectory() | 判断是否是目录 |
mkdir() | 创建一级目录 |
mkdirs() | 创建多级目录 |
delete() | 删除空目录或文件 |
2. IO 流原理和分类
- 按操作数据单位不同分为字节流和字符流
- 按数据流的流向不同分为输入流和输出流
- 按流的角色的不同分为字节流和处理流(包装流)
- 抽象基类: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. 字符流
- FileWriter 使用后,必须 close() 或 flush(),否则数据只是写在缓冲区,未写入文件
- 字符流底层调用字节流实现具体功能
5. 节点流与处理流
- 节点流可以从一个特定的数据源读写数据,如 FileReader、FileWriter
- 处理流(包装流)是 “连接” 在已存在的流,为程序提供了更为强大的读写功能,如 BufferedReader、BufferedWriter
- 节点流是底层流,直接对数据源进行操作
- 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更加方便的方法来完成文件操作,底层调用节点流实现具体功能。优点主要有以下两点:性能的提高:主要以增加缓冲的方式来提高输入输出的效率、操作的便捷:提供了一系列便捷的方法来一次输入、输出大批量的数据(比如 BufferedReader 可以一次读取一行,而 FileReader 最多以字符数组形式进行读取)
- 使用处理流时,只需关闭外层流即可,对应的节点流会自动关闭
处理流设计理念体现了修饰器模式。列表中前 5 行为节点流,其余行对应的流为处理流
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 | |
---|---|---|---|---|---|
1 | 抽象基类 | InputStream | OutputStream | Reader | Writer |
2 | 访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
3 | 访问数组 | ByteArrayInputSteam | ByteArrayOutputStram | CharArrayReader | CharArrayWriter |
4 | 访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
5 | 访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter | |
转换流 | InputStreamReader |