在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。
某些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行打印,我们把子任务称为线程。
每一个进程都是有程序代码组成的,这些代码在进程中 有CPU轮流进行执行
操作系统调度的最小任务单位其实不是进程,而是线程。
因为同一个应用程序,既可以有多个进程,也可以有多个线程
同一个应用程序,既可以有多个进程也可以有多个线程
线程是进程中的一个执行单元/执行路径,负责当前进程中程序的执行
一个进程中至少要有线程,也可存在多个线程的,这种程序我们称之为多线程程序
进程和线程是包含关系,但是多任务既可以由多进程实现,也可以由单进程内的多线程实现,还可以混合多进程+多线程。
具体采用哪种方式,要考虑到进程和线程的特点。
和多线程相比,多进程的缺点在于:
而多进程的优点在于:
多进程稳定性比多线程高,因为在多进程的情况下,一个进程崩溃不会影响其他进程,而在多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。
和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。
多线程编程的复杂度高,调试更困难。
实现多线程有两种方式 继承Thread类和实现Runnable接口
要创建一个新线程,我们需要实例化一个Thread
实例,然后调用它的start()
方法
从Thread
派生一个自定义类,然后覆写run()
方法
一个类继承了Thread类的话 就具备了可以被多线程执行的功能
继承Thread 相当于与获得了一个 执行路径
还差一个 执行任务 重写run函数
run函数不是由我们调用的 由JVM调用
start开辟一个执行路径 由虚拟机具体执行 run
具体步骤:
(1)定义一个类继承Thread
(2)重写run函数
(3)创建子类对象,就是创建线程对象
(4)调用start方法,开启线程并让线程执行,同时告诉JVM去调用run方法
public class Main {
public static void main(String[] args) {
Demo d1 = new Demo("小红");
Demo d2 = new Demo("小明");
System.out.println(d1.getName()); //Thread-0 小红
System.out.println(d2.getName()); //Thread-1 小明
d1.start();
d2.start();
for (int i = 1; i <= 50; i++) {
System.out.println(Thread.currentThread().getName() + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//return
}
}
class Demo extends Thread {
public Demo(String name) {
super(name); //-> Thread() -> "Thread-" + num
}
public void show() {
for (int i = 1; i <= 50; i++) {
System.out.println(getName()); //当前线程对象的名称
//当前代码所在线程对象的名称
System.out.println(Thread.currentThread().getName() + i);
}
}
//run函数描述的就是当前线程需要被执行的任务
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println(Thread.currentThread().getName() + i);
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//一个线程运行的过程中个如果出现异常 则不会影响其他线程的执行!
if (getName().equals("小明") && i == 30) {
throw new NullPointerException();
}
}
}
}
start()
方法会在内部自动调用实例的run()
方法。
创建Thread
实例时,传入一个Runnable
实例
如果某一个类已经继承了别的类 则不能在重新继承Thread
(1)定义类实现Runnable接口 创建一个任务类
(2)重写run函数 就是任务的具体执行内容
(3)外面创建Thread对象 创建执行路径
(4)任务对象与Thread对象进行关联:将Runnable接口的子类对象作为参数传递给Thread的构造函数
(5)调用start()
public class Main {
public static void main(String[] args) {
PrintNumberTask r1 = new PrintNumberTask();
Thread t1 = new Thread(r1);
PrintNumberTask r2 = new PrintNumberTask();
Thread t2 = new Thread(r2);
Thread t3 = new Thread(()->{
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + "-" + i);
}
});
t1.start();
t2.start();
t3.start();
}
}
class PrintNumberTask implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + "-" + i);
}
}
}
用Java8引入的lambda语法进一步简写为:
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start(); // 启动新线程
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("start new thread!");
}
}
要模拟并发执行的效果,我们可以在线程中调用Thread.sleep()
,强迫当前线程暂停一段时间
用多线程的技术去复制目录中指定文件
import java.io.*;
//用多线程的技术去复制目录中指定文件
public class Main {
public static void main(String[] args) {
//创建文件对象
File source = new File("C:\\Users\\jameth\\Desktop\\aab\\JavaSE");
File aim = new File("C:\\Users\\jameth\\Desktop\\Code");
//判断路径是否存在
if (!aim.exists()) {
aim.mkdir();
}
//拷贝文件
traversal(source, aim);
}
private static void traversal(File dir, File aimdir) {
File[] files = dir.listFiles();
if (files != null && files.length != 0) {
for (File file : files) {
if (file.isFile()) {
if (file.getName().endsWith(".java")) {
//创建执行任务对象
CopyFileTask task = new CopyFileTask(file, new File(aimdir, file.getName()));
//创建执行道路 并且关联执行任务
Thread thread = new Thread(task);
thread.start();
}
} else {
traversal(file, aimdir);
}
}
}
}
}
class CopyFileTask implements Runnable {
private File source;
private File aim;
public CopyFileTask(File source, File aim) {
this.source = source;
this.aim = aim;
}
@Override
public void run() {
BufferedInputStream bis = null;
FileInputStream fis = null;
BufferedOutputStream bos = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(source);
fos = new FileOutputStream(aim);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
byte[] buf = new byte[1024 * 1024];
int len = 0;
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
bos.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println(source.getName() + "拷贝完成!");
}
}
Java用Thread
对象表示一个线程,通过调用start()
启动一个新线程;
一个线程对象只能调用一次start()
方法;
线程的执行代码写在run()
方法中;
线程调度由操作系统决定,程序本身无法决定调度顺序;
Thread.sleep()
可以把当前线程暂停一段时间。