1 进程与线程
1.1 进程
开发写的代码称为程序,那么我们将程序运行起来,我们称之为进程;
进程就是申请一块内存空间,将数据放到内存空间中去,是系统进行资源分配和调度的基本单位。
📌 程序与进程的区别
程序是数据和指令的集合,是一个静态的概念,就是一堆代码,可以长时间的保存在系统中;
进程是程序运行的过程,是一个动态的概念,进程存在着生命周期,也就是说进程会随着程序的终止而销毁,不会永久存在系统中。
📢 进程间的交互通过 TCP/IP 端口实现
1.2 进程池
类似于线程池,进程池是资源进程,管理进程组成的应用及技术;不多介绍了,它主要用于处理成千上万的进程任务。
📌 概念
资源进程:预先创建好空的进程,管理进程会把任务分发到空闲进程来处理;
管理进程:负责创建资源进程,把工作交给空闲资源处理,回收已经处理完的资源进程;
1.3 线程
一个进程至少有一个线程(比如java程序至少有main线程和GC线程),线程是进程中的实际运作单位,操作系统能够进行运算调度的最小单位(CPU分配的最小单位)。
一个线程是进程中一个单一顺序的控制流,一个进程可以并发多个线程,每条线程并行执行不同的任务;
一个线程是进程的一条流水线,只用来执行程序,而不涉及到申请资源;
📌 进程和线程的关系
比如说我们用手机打开微信,运行的微信就是开启的一个线程,当我们使用微信的一些功能,扫一扫,付款等,这些都是线程;
进程包含线程,线程属于进程的子集;
📢 多个线程共享同一内存,通过共享的内存空间来交互
1.4 多线程
并发:就是同一时刻,只有一个执行,但是同一个时间段内,两个线程都执行了。并发的实现依赖于CPU切换线程,因为切换的时间特别短,所以基本对于用户来说是无感知的;
并行:是同一时刻,两个线程都在执行,这就要求两个CPU(或多核cpu)分别去执行两个线程;
如下图,分别是并发和并行状,多线程就是实现并发的一个手段。
串行:一个程序处理完当前进程,接着处理下一个进程,一个一个连着进行。
2 线程创建的三种方式
如图,线程创建主要有以下三种方式👇,第二种是重点。
2.1 继承 Thread 类
public class MyThread extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("继承Thread实现了第"+i+"次调用;");
}
}
/**
* 继承thread 实现多线程
* 1. 类继承Thread
* 2. 重写run()
* 3. start() 调用
*/
//main线程--主线程
public static void main(String[] args) {
//创建一个线程对象
MyThread myThread = new MyThread();
//调用start()方法开启线程
myThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("main调用了"+i+"次;");
}
}
}
如果电脑cpu非顶级,那么控制台输出如下:
可以看到它是无序的,这是因为MyThread线程和主线程在交替进行,也就是所谓的并发!
如果将myThread.start()替换为myThread.run(),那么只存在一个主线程,就会先执行run()方法,因此将先输出run()方法中的内容,再输出主线程中的内容,不妨试一下。
2.2 实现 Runable 接口
📢 一般建议使用该方法,因为可以避免Java单继承的局限性。
public class MyRunnable implements Runnable{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("实现Runnable实现了第"+i+"调用;");
}
}
/**
* 实现Runnable接口实现多线程
* 1. 类实现Runnable
* 2. 重写run()
* 3. 将实现类放入Thread并调用start()
*/
public static void main(String[] args) {
//创建runable接口的实现类对象
MyRunnable myRunnable = new MyRunnable();
//创建线程对象,通过线程对象来开启线程,代理,将2行代码简写为1行
// Thread thread = new Thread(myRunnable);
// thread.start();
new Thread(myRunnable).start();
for (int i = 0; i < 20; i++) {
System.out.println("main调用了"+i+"次;");
}
}
}
执行main方法,控制台输出如下:
依旧是多线程的无序性。
2.3 实现 Callable 接口
📌主要有以下步骤:
实现 Callable 接口, 需要返回值
重写 call 方法,需要抛出异常
创建目标对象
创建执行服务:ExecutorService service = Executors.newCachedThreadPool()
提交执行:提交执行:Future<> submit = service. Submit (对象);
获取结果 :Boolean aBoolean = submit. Get ();
关闭服务:service. ShutdownNow ();
public class MyCallable implements Callable<Boolean> {
@Override
public Boolean call() {
return false;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
ExecutorService service = Executors.newCachedThreadPool();
Future<Boolean> submit = service.submit(myCallable);
System.out.println(submit.get());
service.shutdown();
}
}
3 多线程实践举例
3.1 下载网图(继承 Thread 类)
📌 引入jar包
因为我创建的是maven项目,所以直接引入io依赖就行了。
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
📌 编写代码
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习thread实现多线程下载图片
public class ImageThread extends Thread{
private String url;
private String name;
public ImageThread(String url,String name){
this.url = url;//图片地址
this.name = name;//图片地址
}
@Override
public void run() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downloader(url,name);
System.out.println("下载了文件名为:"+name);
}
public static void main(String[] args) {
ImageThread t1 = new ImageThread("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","1.jpg");
ImageThread t2 = new ImageThread("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","2.jpg");
ImageThread t3 = new ImageThread("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","3.jpg");
t1.start();
t2.start();
t3.start();
}
}
//下载器
class WebDownLoader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
执行main方法,输出如下:
说明这三个线程并无严格顺序,是交替进行的。
3.2 下载网图(实现 Runable 接口)
📌 编写代码
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习thread实现多线程下载图片
public class ImageThread implements Runnable{
private String url;
private String name;
public ImageThread(String url,String name){
this.url = url;//图片地址
this.name = name;//图片地址
}
@Override
public void run() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downloader(url,name);
System.out.println("下载了文件名为:"+name);
}
public static void main(String[] args) {
ImageThread t1 = new ImageThread("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","1.jpg");
ImageThread t2 = new ImageThread("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","2.jpg");
ImageThread t3 = new ImageThread("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","3.jpg");
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
}
}
//下载器
class WebDownLoader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.3 模拟抢票(实现 Runable 接口)
📌 编写代码
//多个线程同时操作同一个对象(买火车票)
public class TicketThread implements Runnable {
//票数
private int tickerNums = 10;
@Override
public void run() {
while (true){
if (tickerNums <= 0){
break;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+tickerNums--+"张票");
}
}
public static void main(String[] args) {
TicketThread ticketThread = new TicketThread();
new Thread(ticketThread,"小明").start();//第二个参数是线程命名
new Thread(ticketThread,"小红").start();
new Thread(ticketThread,"小王").start();
}
}
控制台输出如下:
很明显能发现个问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱!
这会在之后文章进行解决~
3.4 模拟龟兔赛跑(实现 Runable 接口)
📌 编写代码
public class Race implements Runnable{
//胜利者--只有一个
private static String winner;
@Override
public void run() {
for (int i = 1; i <= 15; i++) {
//模拟兔子休息,每5步进行休息
if (Thread.currentThread().getName().equals("兔子")&&i%5==0){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否完成
boolean flag = gameover(i);
//若比赛结束,停止循环
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
}
}
//判断比赛是否完成
public boolean gameover(int steps){
if (winner != null){ //不写该限制,会出现两个winer
return true;
}else {
if (steps == 15){
winner = Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
控制台输出如下👇,乌龟成为胜利者,主要是模拟了两个线程进行循环输出。