【Java开发】JUC基础 01:进程、线程、多线程

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 接口

📌主要有以下步骤:

  1. 实现 Callable 接口, 需要返回值

  1. 重写 call 方法,需要抛出异常

  1. 创建目标对象

  1. 创建执行服务:ExecutorService service = Executors.newCachedThreadPool()

  1. 提交执行:提交执行:Future<> submit = service. Submit (对象);

  1. 获取结果 :Boolean aBoolean = submit. Get ();

  1. 关闭服务: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();

    }

控制台输出如下👇,乌龟成为胜利者,主要是模拟了两个线程进行循环输出。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尹煜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值