week04_day03_Thread01
引入多线程:
假如我要实现如下功能
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
同时,当我通过键盘输入固定响应的时候,程序停止向屏幕输出问候的语句
好像很简单,那我们就来实现一下吧。
第一段代码用的单线程。
突然发现,看似很简单的功能,用我们之前的技术,完成不了
分析一下,这里为啥我们完成不了?
核心原因在于两个简单的功能似乎在“同时”执行
如何实现上述功能呢?多线程
多线程究竟做了什么?
从效果上来看,似乎使得同一程序中的不同部分的代码,“同时”运行起来了。
public class SingleThread {
static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
sayHelloRecycling();
//后面的代码都不会执行,程序卡在sayHelloRecycling()的while(true)中
System.out.println("after sayHelloRecycling");
waitToStop();
System.out.println("main end");
}
private static void waitToStop() {
//实现,通过键盘输入固定字符串比如:gun
Scanner scanner = new Scanner(System.in);
while (true) {
String input = scanner.nextLine();
if ("gun".equals(input)) {
flag = false;
break;
}
}
}
private static void sayHelloRecycling() throws InterruptedException {
// 每隔2秒种,通过循环实现,不停的向屏幕输出哈哈, 你好!
while (flag) {
System.out.println("哈哈, 你好!");
//等待三秒钟
Thread.sleep(3000);
}
}
}
第二段代码用的多线程。
/*
* 一个线程,就是一条独立的执行路径
*/
public class MultiThread {
static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
sayHelloRecycling();
waitToStop();
System.out.println("main end");
}
/**
* 在子线程中,接收键盘输入,并根据键盘输入
* ,决定是否终止,在屏幕上输出问候语句
*/
private static void waitToStop() {
new Thread() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
while (true) {
String s = scanner.nextLine();
if ("gun".equals(s)) {
flag = false;
scanner.close();
break; //如果输入了gun,就终止程序的运行,我上课是出现的
}
}
}
}.start();
}
/**
* 在子线程中,不停的在屏幕上输出问候语句,
* 直到,循环的控制变量flag的值被改为false
*/
private static void sayHelloRecycling() throws InterruptedException {
new Thread() {
@Override
public void run() {
while (flag) {
System.out.println("哈哈, 你好!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
单线程与多线程执行路径对比:
·················································································································································································································
前提:计算机中,CPU就是专门用来做运算,一切运算都是由CPU,而且CPU它是我们计算机中最宝贵的资源
其实,我们是把CPU的用来计算时间,当作是一种资源
只有一个CPU,单核
为什么会有多进程?
-
单道批处理:
单道:在整个操作系统中,同一时间,内存中只有 一个程序 在运行,程序的运行,只能是上一个程序运行完,下一个程序开始运行
批处理:在程序运行过程中,不会有任何响应,直到程序运行结束。单道批处理操作系统,它并不能很好的利用CPU的计算时间
比如:假设,在单道批处理系统中,运行了一个程序,假设在它的程序中,它需要执行IO(和打印机传输数据),IO的数据传输过程中
有很大一部分时间,是不会用到CPU的计算功能,此时CPU闲置。 -
多道批处理操作系统? 就是为了提高CPU的利用率
多道:在操作系统中,同时内存中,可以有多个应用程序,在运行,这样一来,一旦某个应用程序,不需要使用cpu的计算功能,我们,操作系统就会把cpu计算时间,分配给内存中的其他应用程序来计算。这样一来,cpu就大大提高了。
应用程序的执行:当应用程序,占用cpu执行时间,这个应用才算真正的执行
在多道批处理系统中,多个应用程序,交替执行,看起来好像多个应用程序在“同时”运行核心原因:进程的交替执行,交替过程,是需要付出额外代价的 —— 进程的上下文切换
-
现代操作系统,引入了另外一个东西,线程
线程,又被称之为轻量级进程,一个进程中可以有多个线程
同一个进程中的多个线程,线程上下文切换的时候,付出额外代价,小的多并发: 并发执行,一段时间内,多个程序,同时运行
并行(多CPU): 并行执行,同一个时间点,多个程序同时运行通常我们生活中所说的同时,指的是并行
一个进程中有多个线程,进行线程切换时,只需要切换一小部分资源即可。
如果没有线程,进程的上下文切换时我们需要保存一个进程的很多东西,同时又需要恢复另一个线程的很多很多东西。这样就十分耗费CPU资源,耗费时间。
·················································································································································································································
每运行一个java程序都会创建一个新的进程
Java命令运行一个Java程序的过程?
JVM是单线程还是多线程?
package com.cskaoyan.thread.basic;
/**
* @author shihao
* @create 2020-04-29 16:51
*
*
* 1. Java命令运行一个Java程序的过程? java 字节码文件名
a. 其实java命令,它启动了一个jvm进程
b. 该jvm进程,在执行的时候,首先会创建一个线程,main线程
c. 在main线程中,运行主类中的main方法代码
2. JVM是单线程还是多线程? Jvm天生多线程(用垃圾回收器的执行,证明)
*/
public class Demo01 {
public static void main(String[] args) {
while (true) {
//占用堆空间,占用完之后就变成了垃圾。
int[] array = new int[8192];
array = null;
//其实,在jvm,还有另外一个线程, 在执行垃圾回收器的垃圾回收代码
//所以jvm天生多线程。只不过我们直观感受到的是main线程
//你不停的产生垃圾,垃圾回收器也在不停的回收垃圾。
}
}
}
·················································································································································································································
Thread是具体类,不是抽象类
Thread实现方式一:
1.继承Thread
2.重写子类的run方法
3.创建该子类的对象
4.启动线程 start()
注意事项:
-
一个Thread类(Thread子类) 对象 代表一个线程
-
为什么我们重写Thread类中的run方法
——> 只有Thread run()方法中的代码,才会执行在子线程中
为了保证,子线程中运行的是我们想要在子线程中运行的代码 -
但是,如果想要让代码,在子线程中运行,并非一定,代码要写在run方法方法体中。对于,定义在该Thread子类中,其他方法方法体中的代码(如test()),也可以运行在子线程。
换句话说,一个方法,被哪个线程中的代码调用,被调用的方法,就运行在,调用它的线程中。(test()方法被run()方法调用就运行在MyFirstThread对象中,如果别main方法调用就运行在main线程中) -
启动线程,必须使用start()方法来启动,这样才能使Thread中的run方法运行在子线程中
如果, 如果通过调用run方法,来执行Thread的run方法代码,这仅仅只是普通的方法调用 -
同一个Thread或Thread子类对象(代表同一个线程),只能被启动一次
如果,我们要启动多个线程,只能创建多个线程对象,并启动这些线程对象
这和线程的状态有关系,一个线程只能从一个新建状态转化为死亡状态,死亡后就变成垃圾
public class Demo02 {
public static void main(String[] args) {
//3.创建该子类的对象
MyFirstThread thread = new MyFirstThread();
//启动线程 start(), 是通过start方法启动线程
//thread.start();
// 测试启动线程, 这种方式,并不能启动线程
//thread.run();
//System.out.println("main end");
// 多次启动线程, 同一线程启动两次(这是错误的方式,同一个线程只能被启动一次)
//thread.start();
//thread.start();
// 如果要启动多个线程,那就创建多个线程对象并启动
MyFirstThread thread1 = new MyFirstThread();
MyFirstThread thread2 = new MyFirstThread();
thread1.start();
thread2.start();
}
}
//1.继承Thread
class MyFirstThread extends Thread {
// 2.重写子类的run方法
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 只有Thread类的run方法中的代码,才会执行在子线程中
System.out.println("hello, thread");
//在run方法中被调用,该方法就运行在子线程中
//test();
}
public void test() {
}
}
·················································································································································································································
作业:复制目录,要求:
a. 复制目录及其所有子目录,保证在复制的目标目录中,目录结构和原目录相同
b. 同时,对于原目录及其子目录,只将原目录中的指定类型的.java文件,复制到目标目录中,对应的相同目录中
package com.cskaoyan.thread;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* @author shihao
* @create 2020-04-29 21:13
*/
public class test {
public static void main(String[] args) throws IOException {
//比如,要在e盘中复制d盘的dir目录
multiCopy(new File("F:\\dir"), new File("F:\\test\\dir"));
}
/**
* @param src 当前待复制的目录
* @param dest 在dest目录下,复制src目录
* @throws IOException
*/
public static void multiCopy(File src, File dest) throws IOException {
//如果src是文件
File[] files = src.listFiles();
if (files == null) {
return;
}
//获取待复制文件目录的名字
String name = src.getName(); //dir
// targetDir就是对src目录的复制
File targetDir = new File(dest, name); //创建了和src平级的父目录 dir
// targetDir复制该目录本身,它就是对src目录的复制目录,但该目录内容为空
targetDir.mkdirs();
/*
遍历src目录的子文件或子目录,并在复制的目标目录targetDir中,复制src中的子文件或子目录
*/
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
//如果是目录,就递归复制当前目录
multiCopy(files[i], targetDir);
} else {
//如果是文件
if (files[i].getName().endsWith(".txt")) {
// 而且是java文件
File targetFile = new File(targetDir, files[i].getName());
// 则在targetDir目录中,复制src目录中的java文件
copyFile(files[i], targetFile);
}
}
}
}
/*
将源文件src,复制到目标文件dest中
*/
public static void copyFile(File src, File dest) throws IOException {
FileReader fr = new FileReader(src);
FileWriter fw = new FileWriter(dest);
int len;
char[] buffer = new char[1024];
while ((len = fr.read(buffer)) != -1) {
fw.write(buffer, 0, len);
}
fr.close();
fw.close();
}
}