多线程(一) 线程概念及创建线程的方法

多线程(一) 多线程基础


前言

我只是一个新上路的小菜鸟,通过分享自己的所学来巩固知识,文中如有错误希望能够指出改正。

一、线程的相关概念

程序

程序是为了完成特定任务,用某种语言编写的一组指令的集合。简单的说:就是我们写的代码。程序并不等于软件,软件是程序以及开发、使用和维护所需要的所有文档的总称,而程序是软件的一部分。 例如:下图就是一个获取计算机CPU个数的程序
在这里插入图片描述

进程

进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。进程是程序的一次执行过程,或是正在运行的一个程序。进程是一个动态过程:有它自身的产生、存在和消亡的过程。
在这里插入图片描述
比如这里打开了三个PotPlayer窗口,则有三个PotPlayer进程存在,它们每一个进程都会占用电脑资源,当关闭进程时会释放资源。

线程

a. 线程是由进程创建的,是进程的一个实体
b. 一个进程可以有多个线程,比如迅雷这个进程同时下载多个应用,那么每一个下载任务都是一个线程
在这里插入图片描述
或者在QQ进程中打开了多个聊天窗口,每一个窗口都是一个线程。

其他相关概念

● 单线程:同一个时刻只允许运行一个线程
● 多线程:同一个时刻可以允许运行多个线程,比如:一个qq可以同时打开多个聊天窗口
● 并发:同一个时刻,多个任务交替执行,造成一种"好像是同时运行"的错觉,简单的说单核cpu实现的多任务就是并发
在这里插入图片描述
并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。
在这里插入图片描述

二、线程的基本使用

创建线程的方式共有两种,分别是基础Thread类,重写run方法和实现Runnable接口,重写run方法。
Thread(线程)类的结构:
在这里插入图片描述

1.通过继承Thread类创建线程

应用案例:开启一个线程,该线程每隔1秒。在控制台输出"喵喵,我是小猫咪"
进行改进:当输出80次时,结束该进程

代码如下:

package com.maozaicc.threaduse;

/**
 * 开启一个线程,该线程每隔1秒。在控制台输出"喵喵,我是小猫咪"
 * 进行改进:当输出80次时,结束该进程
 */
public class Thread01 {
    public static void main(String[] args) {
        //创建 Cat 对象,可以当做线程使用
        Cat cat = new Cat();
        cat.start();
    }
}
//当一个类继承了 Thread 类, 该类就可以当做线程使用
class Cat extends Thread {// 继承Thread类来创建线程
    //这种写法只执行一次,原因是run并没有循环,执行一次就结束了
//    @Override
//    public void run() {// 在重写的run()方法中写代码实现自己的逻辑
//        System.out.println("喵喵,我是小猫咪");
//    }
    //要输出80次可以设计一个计数器来控制
    int nums = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("喵喵,我是小猫咪,我的编号是" + (++nums));
            if (nums == 80) {
                break;
            }
            try {
                Thread.sleep(100);//程序运行太快加sleep函数可以更清楚看到输出过程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

以上程序设计到的几个问题:

  1. 为什么调用方法要写Cat.start()不直接写Cat.run()呢?
    首先要搞清楚run()方法它只是一个普通的方法而已,如果直接写Cat.run(),则相当于cat对象去调用了以下run()方法,程序是串行化执行的,如果在cat.start();后面还有语句,则必须等待cat.start();执行完成才会执行,并没有创建线程。
  2. 源码分析
    start()方法中最核心的一条语句是调用start0()方法:
public synchronized void start() { start0(); }//只写了一句最核心的start0,其实源码很长

关于start0()方法的说明:

private native void start0();

● start0() 是本地方法,由 JVM 调用, 底层是 c/c++实现
● 真正实现多线程的效果, 是 start0(), 而不是 run()方法
● start0()方法是Runnable接口中的方法
为了验证以上问题,可设计如下程序:

package com.maozaicc.threaduse;

/**
 * 部分输出结果:
 * 主线程继续运行...线程名:main
 * 喵喵,我是小猫咪,我的编号是1 线程名=Thread-0
 * main线程中的i = 0
 * 喵喵,我是小猫咪,我的编号是2 线程名=Thread-0
 * main线程中的i = 1
 * main线程中的i = 2
 * 喵喵,我是小猫咪,我的编号是3 线程名=Thread-0
 * main线程中的i = 3
 * 喵喵,我是小猫咪,我的编号是4 线程名=Thread-0
 * 喵喵,我是小猫咪,我的编号是5 线程名=Thread-0
 */
public class Thread02 {
    public static void main(String[] args) throws InterruptedException {
        //创建 Cat 对象,可以当做线程使用
        Cat1 cat = new Cat1();
        cat.start();
        System.out.println("主线程继续运行...线程名:" + Thread.currentThread().getName());//验证main线程和cat线程是否堵塞
        for (int i = 0; i < 60; i++) {
            System.out.println(Thread.currentThread().getName() + "线程中的i = " + i);
            Thread.sleep(500);//让main线程睡眠
        }
    }
}

class Cat1 extends Thread {// 继承Thread类来创建线程

    int nums = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("喵喵,我是小猫咪,我的编号是" + (++nums) + " 线程名=" + Thread.currentThread().getName());
            if (nums == 80) {
                break;
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

由部分输出结果我们可以看出:
● 当程序开始执行时进程执行main()函数就会产生一个main线程
● 调用cat.start();会创建一个进程,名字是Thread-0,这个进程不会与main线程堵塞
● 根据输出结果的两种输出可以看出两个进程是交替执行的,并且在进程中main()线程结束并不是进程结束的判断依据,只有当进程中的所有线程都结束后进程才会结束
执行流程图:
在这里插入图片描述
其中main线程和Thread-0线程都可以产生多个线程。只有当所有线程都结束时进程才会结束。
start()方法调用了start0()方法后,线程并不一定会立马执行,只是将线程变为了可执行状态。具体什么时候执行由CPU统一调度。相当于玩游戏机,只有一个游戏机(cpu),可是有很多人要玩,于是,start是排队!等CPU选中你就是轮到你,你就run(),当CPU的运行的时间片执行完,这个线程就继续排队,等待下一次的run()。

2.通过实现Runnable 接口创建线程

什么时候采用这种方式呢?我们知道Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类方法来创建线程显然是不可能的,我们就要通过实现Runnable接口来创建线程。

应用案例:编写程序,该程序可以每隔1秒,在控制台输出"Hi!",当输出10次后,自动退出,使用实现Runnable接口的方式实现。这里底层使用了代理模式(软件设计模式)
代码如下:

package com.maozaicc.threaduse;


/**
 * 使用实现Runnable接口的方式来创建线程
 */
public class Thread03 {
    public static void main(String[] args) {
        sayHi hi = new sayHi();
        /**
         * hi.start();这里调用start()方法会报错,因为start()方法是Runnable接口中的方法,这里并没有start()的定义
         * Thread类中有这样一个构造器:
         *  public Thread(Runnable target) {
         *         init(null, target, "Thread-" + nextThreadNum(), 0);
         *     }
         * 它可以接收一个Runnable接口类型对象,而这里sayHi类实现了Runnable接口,
         * 根据接口的多态性:接口类型变量可以指向实现了接口的类的实例,则可以将sayHi类对象传递进去
         * 所以此时target对象其实指向的是定义的hi对象。
         * 因为thread对象的编译类型是Thread,所以可以调用Thread接口的start()方法,然后调用start0()方法
         */
        Thread thread = new Thread(hi);
        thread.start();
    }
}

class sayHi implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Hi,i = " + i);
        }
    }
}

3.多线程执行

应用案例:编写程序,创建两个线程,一个线程每隔1秒输出"Dog汪汪汪…“,输出10次退出,另一个线程输出"Cat喵喵喵…”,输出五次退出。
分析:该案例是要在main方法中创建两个线程,则一共有三个线程(还有main线程),main线程是最先退出的

package com.maozaicc.threaduse;

/**
 * 创建两个线程,一个线程每隔1秒输出"Dog汪汪汪...",输出10次退出,另一个线程输出"Cat喵喵喵...",输出五次退出
 */
public class Thread04 {
    public static void main(String[] args) {
        DogT dogT = new DogT();
        CatT catT = new CatT();
        Thread thread = new Thread(dogT);
        Thread thread1 = new Thread(catT);
        thread.start();//启动第一个线程
        thread1.start();//启动第二个线程

    }
}

class DogT implements Runnable {//线程1

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Dog汪汪汪..." + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class CatT implements Runnable {//线程2

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Cat喵喵喵..." + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4.线程的理解

在这里插入图片描述
猴子就相当于我们的一个进程,它一共创建了两个线程,分别是扫地和摘果,只有当这两个线程都结束时猴子这个进程才会结束。

4.创建线程的两种方式比较

  1. 从java的设计上来看,两种创建线程的方式本质上没有区别,本质都是调用Runnable接口的start()方法然后调用start0()方法来创建的;
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,推荐采用这种方式,分析如下代码:
// T3是一个实现Runnable接口的类
T3 t3 = new T3("Hello");
Thread thread01 = new Thread(t3);
Thread thread02 = new Thread(t3);
thread01.start();
thread02.start();

分析:在这个程序中,两个线程共用t3这一个资源,如果采用继承Thread类的方式,创建两个线程需要创建两个对象,而对象又是独立的个体存在,所以无法实现共用资源。

总结

以上就是线程继承的部分内容,本文仅仅简单介绍了线程的概念以及线程的创建方式,后续学习过程中还会继续分享。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值