Java中多线程

目录

一、概述

1、多线程和单线程

2、程序执行原理

3、主线程

二、线程的创建

1、继承 Thread 类创建线程(方法一)

2、实现 Runnable 接口创建线程(方法二)

三、线程的匿名内部类


一、概述

1、多线程和单线程

多线程即一个程序中有多个线程在同时执行,单线程和多线程:

  • 单线程:若有多个任务,只有当上一个任务执行结束后,下一个任务才开始执行
  • 多线程:若有多个任务,可以同时执行

2、程序执行原理

在操作系统中,有很多种调度方式,这里介绍分时调度和抢占式调度,在Java中使用的是抢占式调度,所以主要介绍抢占式调度方式

  • 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间
  • 抢占式调度:每个线程都有其优先级,优先让优先级高的进程使用 CPU,如果线程优先级相同,则会随机选择去执行
    (1) CPU 使用抢占式调度模式在多个线程间进行着高速的切换
    (2) 对于 CPU 的一个核而言,某个时刻只能执行一个线程,而 CPU 在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻执行
    (3) 多线程程序并不能提高程序的运行速度,但能提高程序运行效率,让 CPU 的使用率更高

3、主线程

Java程序在执行过程中,先启动 JVM,并加载对应的 class 文件,JVM 并会从 main 方法开始执行我们的程序代码,一直把 main 方法的代码执行结束。在 JVM 启动后,必然有一个执行路径(线程)从 main 方法开始,一直执行到 main 方法结束,这个线程在 Java 中称之为主线程。当程序的主线程执行时,如果遇到了循环而导致程序在指定位置停留时间过长,则无法马上执行下面的程序,需要等待循环结束后才能够执行,那么,能否实现一个主线程负责执行其中一个循环,再由另一个线程负责其他代码的执行,最终实现多部分代码同时执行的效果呢?办法总比困难多,多线程便孕育而生,很好的解决了这个问题!

二、线程的创建

创建新执行线程有两种方法

  • 方法一:类声明为 Thread 的子类,该子类应重写 Thread 类的 run 方法,然后创建对象,开启线程。run 方法相当于其他线程的 main 方法
     
  • 方法二:声明一个实现 Runnable 接口的类,该类然后实现 run 方法,然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程

1、继承 Thread 类创建线程(方法一)

(1) 创建步骤及分析

  • 定义一个类继承 Thread 类
  • 重写 run 方法
  • 创建子类对象,即创建线程对象
  • 调用 star 方法,开启线程并让线程执行,同时还会告诉 JVM 去调用 run 方法

自定义线程类:

public class myThread extends Thread {
    //重写run方法,完成该线程执行的逻辑
    public void run()
    {
        for(int i = 0;i < 50;i++)
        {
            System.out.println("新线程正在执行" + i);
        }
    }
}

在主线程中调用:

public static void main(String[] args)
{
    //创建自定义线程对象
    myThread mT = new myThread();
    //开启新线程,让新的线程执行程序,jvm调用线程中的run
    mT.start();
    //在main方法中执行
    for(int i = 0;i < 50;i++)
    {
        System.out.println("main线程正在执行" + i);
    }
}

分析:

  • 在主线程中创建自定义线程对象,调用star方法开启新线程,让新的线程执行程序,jvm再调用线程中的run方法
  • 创建新的线程后,会产生两个执行路径,都会被CPU执行,CPU有自己的选择执行权力,所以会出现随机的执行结果
  • 可以理解为两个线程在抢夺CPU的资源(时间)
  • 注:线程对象调用 run 方法和调用 star 方法的区别:
    (1) 线程对象调用 run 方法不开启线程,仅仅是对象调用方法
    (2) 线程对象调用 star 方法开启线程,并让 JVM 调用 run 方法在开启的线程中执行

问题分析:

为什么要继承 Thread 类,并调用 star 方法才能开启线程,为什么不像下面代码一样直接创建 Thread 类的对象呢?

Thread t1 = new Thread();
t1.star();
  • 创建线程的目的是为了建立程序单独的执行路径,让多部分代码实现同时执行,而以上代码,直接创建 Thread 类的对象,虽然没有语法错误,不会报错,但该 star 调用的是Thread 类中的 run 方法,而这个方法什么也没做,更重要的是这个 run 方法中并没有定义我们需要让线程执行的代码
  • Thread 类 run 方法中的任务并不是我们所需要的,只有重写这个 run 方法,Thread 类已经定义了线程任务的编写位置(run 方法),那么只要在 run 方法中定义任务代码即可,即重写 run 方法

(2) 多线程内存理解

  • 多线程在执行的时候,是在栈内存中的,每一个执行线程都有一片自己的所属栈内存空间,进行方法的压栈和出栈
  • 当执行线程的任务结束了,线程自动在栈内存中释放,当所有的执行线程都结束了,进程也就结束了

(3) 获取和设置线程名称

  • String getName():返回该线程的名称(同 Thread.currentThread().getName())
  • Thread.currentThread():获取当前线程对象
  • Thread.currentThread().getName():获取当前线程对象的名称
  • setName():设置线程名称
public static void main(String[] args)
{
    myThread mT = new myThread();
    mT.start();
    mT.setName("oneStar");    //设置线程名称
    //System.out.println(getName());  //使用getName()方法获取线程名称 在新线程中可以这样使用
    System.out.println(Thread.currentThread().getName());   //使用Thread.currentThread().getName()获取线程名称
    System.out.println(Thread.currentThread()); //获取当前线程对象
}

(4) Thread 类 sleep 方法

sleep 方法是一个静态的方法,可以通过 Thread 类直接调用,会有异常抛出

public static void main(String[] args) {
    myThread mT = new myThread();
    mT.start();
    //使用sleep方法延时打印for循环
    for(int i = 0;i < 5;i++)
    {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }
}

2、实现 Runnable 接口创建线程(方法二)

Runnable 接口用来指定每一个线程要执行的任务,包含了一个 run 的无参数抽象方法,需要由接口实现重写该方法。此创建线程的方法是声明实现 Runnable 接口的类,该类实现 run 方法,然后创建 Runnable 的子类对象,传入到某个线程的构造方法中,开启线程

(1) 创建步骤:

  • 定义类实现 Runnable 接口
  • 重写接口中的 run 方法
  • 创建 Thread 类的方法
  • 将 Runnable 接口的子类对象作为参数传递给 Thread 类的构造函数
  • 调用 Thread 类的 star 方法开启线程

定义实现类接口

//定义实现类接口
public class myRunnable implements Runnable {
    //重写run方法
    public void run()
    {
        for(int i = 0;i < 5;i++)
        {
            System.out.println("myRunnable线程正在执行!");
        }
    }
}

在主线程中调用

public static void main(String[] args)
{
    //创建线程执行目标类对象
    myRunnable mR = new myRunnable();
    //将Runnable接口的子类对象作为参数传递给Thread类的构造函数
    Thread t1 = new Thread(mR);
    Thread t2 = new Thread(mR);
    //开启线程
    t1.start();
    t2.start();
    for(int i = 0;i < 5;i++)
    {
        System.out.println("main线程正在执行!");
    }
}

(2) 实现 Runnable 接口的好处

  • 避免了单继承的局限性,所以此方法较为常用
  • 将线程分为两部分,一部分线程对象,一部分线程任务,更加符合面向对象思想(继承 Thread 类线程对象和线程任务耦合在一起)
  • 将线程任务单独分离出来封装成对象,类型就是 Runnable 接口类型
  • Runnable 接口对线程对象和线程任务进行解耦,降低紧密性或依赖性,创建线程和执行任务不绑定

三、线程的匿名内部类

使用线程的匿名内部类方式,可以方便的实现每个线程执行不同线程任务操作

方法一:重写 Thread 类中的方法创建线程

new Thread() {
    public void run() {
        for (int x = 0; x < 40; x++) {
            System.out.println(Thread.currentThread().getName()
                    + "...X...." + x);
        }
    }
}.start();

方法二:使用匿名内部类的方式实现 Runnable 接口,重写 Runnable 接口中的 run 方法

Runnable r = new Runnable() {
    public void run() {
        for (int x = 0; x < 40; x++) {
            System.out.println(Thread.currentThread().getName()
                    + "...Y...." + x);
        }
    }
};
new Thread(r).start();

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ONESTAR博客

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

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

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

打赏作者

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

抵扣说明:

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

余额充值