Java的多线程Thread---API文档深入研究1.0

本文详细解析了Java线程、进程概念,创建线程的两种方式(继承Thread和实现Runnable),run()方法和start()方法的区别,以及线程调度、优先级设置和线程名称管理。实例演示了如何设置和获取线程名,并探讨了面试中可能遇到的相关问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

Thread

进程

线程

举例

了解三个名词

java程序的运行原理

创建线程的第一种方式:继承Thread类

run()方法

start()

面试题

模拟多线程

获取和设置线程的名字

1、通过构造方法设置名字Thread(String name)

2、通过Thread类void setName(String name)

3、获取当前方法所在的线程名称 currentThread

线程调度

线程有两种调度模型

如何设置和获取线程优先级

1、获取线程的优先级getPriority()

2、设置线程的优先级setPriority()


Thread

查看API文档我们知道:

public class Thread
extends Object
implements Runnable

线程是程序中执行的线程。 Java虚拟机允许应用程序同时执行多个执行线程。

每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。 每个线程可能也可能不会被标记为守护程序。 当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护线程。

当Java虚拟机启动时,通常有一个非守护进程线程(通常调用某些指定类的名为main的方法)。 Java虚拟机将继续执行线程,直到发生以下任一情况:

创建一个新的执行线程有两种方法。 一个是将一个类声明为Thread的子类。 这个子类应该重写run类的方法Thread 。 然后可以分配并启动子类的实例。 例如,计算大于规定值的素数的线程可以写成如下:

  • 已经调用了Runtime类的exit方法,并且安全管理器已经允许进行退出操作。
  • 所有不是守护进程线程的线程都已经死亡,无论是从调用返回到run方法还是抛出超出run方法的run
     class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

然后,以下代码将创建一个线程并启动它运行:

     PrimeThread p = new PrimeThread(143);
     p.start();
 

另一种方法来创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。 这种其他风格的同一个例子如下所示:


     class PrimeRun implements Runnable {
         long minPrime;
         PrimeRun(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

然后,以下代码将创建一个线程并启动它运行:

     PrimeRun p = new PrimeRun(143);
     new Thread(p).start();
 

每个线程都有一个用于识别目的的名称。 多个线程可能具有相同的名称。 如果在创建线程时未指定名称,则会为其生成一个新名称。

除非另有说明,否则将null参数传递给null中的构造函数或方法将导致抛出NullPointerException

 通过观察API文档我们引出多线程的概念:

进程

  • 正在运行的程序,是系统进行资源分配和调用的独立单位。
  • 每一个进程都有它自己的内存空间和系统资源

线程

  • 是进程中的单个顺序控制流,是一条执行路径
  • 一个进程如果只有一条执行路径,则称为单线程程序
  • 一个进程如果有多条执行路径,则称为多线程程序

举例

百度网盘的下载、植物大战僵尸、杀毒软件

了解三个名词

  • 串行:指的是所有的任务都是按照先后顺序执行的,在前一个任务没处理完的情况下是不会处理下一个任务的,就像理发店只有一个理发师,每个人剪头发的时候都需要等待前面的人剪完。
  • 并行:指的是将任务分给不同的处理器去处理,每一个处理器中的处理是串行的,比如火车站售票会有多个窗口。
  • 并发:实质上是一种现象,并发需要处理器的支持,比如在处理一个任务的时候,操作系统可以进行调度其他的任务,不论是串行还是并行,都需要操作系统的支持并发。假设写代码是一个任务,每个程序员在写代码的同时也能喝咖啡,就是说明支持并发。

java程序的运行原理

  java命令会启动一个java虚拟机,启动JVM,相当于启动了一个应用程序 也就是启动了一个进程。 该进程会自动启动一个“主线程”,然后由这个主线程去调用某个类的main方法main方法运行在主线程中的。我们之前写的程序都是单线程程序。


接下来就让我们简单的创建一个多线程案例玩一下吧

参考代码:
创建Person类

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

创建ThreadDemo类:

public class ThreadDemo {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person();
        Person p3 = new Person();
        Person p4 = new Person();
        Person p5 = new Person();
        Person p6 = new Person();
        Person p7 = new Person();
        Person p8 = new Person();
        Person p9 = new Person();
        //...pn

        for (int i =0;i<10000;i++){
            Person p = new Person();
        }
    }

}

那如何启动线程呢? 请往下看!


创建线程的第一种方式:继承Thread类

步骤:

  • 自定义一个类继承Thread类
  • 自定义类重写Thread类中的run()方法
  • 创建子类对象
  • 启动线程

run()方法

public void run()

如果这个线程是使用单独的Runnable运行对象构造的,则Runnable对象的run方法; 否则,此方法不执行任何操作并返回。

Thread的Thread应该覆盖此方法。

Specified by:

run在界面 Runnable

另请参见:

start()stop()Thread(ThreadGroup, Runnable, String)

为什么要重写run方法呢?

  • 每个线程实现的功能都不一样,所以要重写
  • 如果要启动一个线程就会调用run方法,只有当需要被线程执行的时候,再将被需要的代码写到run方法里

  编写自己的逻辑代码,一般来说,被执行的线程代码都是比较耗时的,为了模拟耗时,我们用循环模拟。

创建MyThread1类:

public class MyThread1 extends Thread{
    //重写run()方法
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(i);
        }
    }
}

 参考代码1:

public class ThreadTestDemo1 {
    public static void main(String[] args) {
        //创建子类对象,每new一次相当于创建了一个线程
        MyThread1 ti = new MyThread1();

        //调用run()方法
        ti.run();
    }
}

运行结果:

通过运行结果来看,我们重写的run方法被成功的调用了,但是这是单线程,那我们调用两次run()方法会发生什么呢?

参考代码2:

public class ThreadTestDemo1 {
    public static void main(String[] args) {
        //创建子类对象,每new一次相当于创建了一个线程
        MyThread1 ti = new MyThread1();

        //调用run()方法
        ti.run();

        //第二次调用run()方法
        ti.run();
    }
}

  运行结果:

通过运行结果我们发现:单纯调用run()方法仅仅表示的是普通的方法调用,所以这里是单线程执行的,想要调用指定的启动线程的方法:start()


start()

查看API文档我们知道;

public void start()

导致此线程开始执行; Java虚拟机调用此线程的run方法。

结果是两个线程同时运行:当前线程(从调用返回到start方法)和另一个线程(执行其run方法)。

不止一次启动线程是不合法的。 特别地,一旦线程完成执行就可能不会重新启动。

异常

IllegalThreadStateException - 如果线程已经启动。

参考代码3:

public class ThreadTestDemo1 {
    public static void main(String[] args) {
        //创建子类对象,每new一次相当于创建了一个线程
        MyThread1 ti = new MyThread1();

        //调用run()方法
        ti.run();

//        //第二次调用run()方法
//        ti.run();

        //启动线程
        ti.start();

    }
}

运行结果:

 通过运行结果我们知道:start()方法的调用,首先启动一个线程,然后由JVM去调用该线程的run方法。

那我们调用两次start()方法会出现异常吗?

参考代码4:

public class ThreadTestDemo1 {
    public static void main(String[] args) {
        //创建子类对象,每new一次相当于创建了一个线程
        MyThread1 ti = new MyThread1();

        //调用run()方法
        ti.run();

//        //第二次调用run()方法
//        ti.run();

        //启动线程
        ti.start();
        
        //第二次启动线程
        ti.start();
        //IllegalThreadStateException:非法的线程状态异常
        //因为调用了两次start()方法,相当于t1这个线程被调用了两次,而不是启动了两个线程的意思
    }
}

 运行结果:

  通过运行结果我们发现:IllegalThreadStateException:非法的线程状态异常,因为调用了两次start()方法,相当于t1这个线程被调用了两次,而不是启动了两个线程的意思。

面试题

调用run()方法和调用start()方法的区别?

  • run()方法的调用仅仅是封装了线程执行的代码,但是直接调用的话与普通方法调用没有任何区别
  • start()方法的调用,首先启动一个线程,然后由JVM去调用该线程的run方法


模拟多线程

至少创建2个或者2个以上的线程

public class ThreadTestDemo1 {
    public static void main(String[] args) {

        MyThread1 t1 = new MyThread1();
        MyThread1 t2 = new MyThread1();

        t1.start();
        t2.start();

    }
}

运行结果 :

 

由于博主的CPU性能比较强大,但是可以看出这里是两个线程启动了。


获取和设置线程的名字

1、通过构造方法设置名字Thread(String name)

 查看API文档我们知道:

public Thread(String name)

分配一个新的Thread对象。 此构造具有相同的效果Thread (null, null, name)

参数

name - 新线程的名称

创建MyThread2:(返回此线程的名称)

public class MyThread2 extends Thread{
    public MyThread2() {
        super();
    }

    //返回此线程的名称
    public MyThread2(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0;i < 100;i++){
            System.out.println(getName() + ":" + i);

        }
    }
}

参考代码:

//通过构造方法给线程起名字
public class MyThreadDemo3 {
    public static void main(String[] args) {
        //创建线程对象
        MyThread2 t1 = new MyThread2("刘德华");
        MyThread2 t2 = new MyThread2("张学友");
        t1.start();
        t2.start();
    }
}

运行结果:


2、通过Thread类void setName(String name)

查看API文档我们知道:

public final void setName(String name)

将此线程的名称更改为等于参数name

首先调用这个线程的checkAccess方法,没有参数。 这可能会导致投掷SecurityException

参数

name - 这个线程的新名称。

异常

SecurityException - 如果当前线程不能修改此线程。

参考代码:

public class MyThreadDemo3 {
    public static void main(String[] args) {

        //创建线程对象
        MyThread2 t1 = new MyThread2();
        MyThread2 t2 = new MyThread2();
        MyThread2 t3 = new MyThread2();

        //通过setName给线程起名字
        t1.setName("刘德华");
        t2.setName("张学友");
        t3.setName("郭富城");

        //启动线程 (线程的执行具有随机性)
        t1.start();
        t2.start();
        t3.start();

    }
}

运行结果:


3、获取当前方法所在的线程名称 currentThread

查看 API文档我们知道:

public static Thread currentThread()

返回对当前正在执行的线程对象的引用。

结果

当前正在执行的线程。

参考代码:

public class MyThreadDemo3 {
    public static void main(String[] args) {

        //public static Thread currentThread()
        //获取当前方法所在的线程名称
        String name = Thread.currentThread().getName();
        System.out.println("当前方法所在的线程名称为:" + name);

    }
}

运行结果:

当前方法所在的线程名称为:main


线程调度

  假如们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

线程有两种调度模型

  • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
  • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
  • java使用的是抢占式调度模型

如何设置和获取线程优先级

创建MyPriorityThread类:

public class MyPriorityThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

1、获取线程的优先级getPriority()

查看API文档我们知道:

public final void setPriority(int newPriority)

更改此线程的优先级。

首先调用这个线程的checkAccess方法,没有参数。 这可能会导致投掷SecurityException

否则,该线程的优先级设置为指定的小newPriority和最大允许的线程的线程组的优先级。

参数

newPriority - 设置此线程的优先级

异常

IllegalArgumentException - 如果优先级不在 MIN_PRIORITYMAX_PRIORITY

SecurityException - 如果当前线程不能修改此线程。

参考代码:

public class ThreadPriorityDemo {
    public static void main(String[] args) {
        //创建2个或者2个以上的线程对象
        MyPriorityThread t1 = new MyPriorityThread();
        MyPriorityThread t2 = new MyPriorityThread();
        t1.setName("张学友");
        t2.setName("郭富城");

        //获取线程的优先级
        int p1 = t1.getPriority();
        System.out.println("张学友的线程优先级为:" + p1);
        int p2 = t2.getPriority();
        System.out.println("郭富城的线程优先级为:" + p2);

        t1.start();
        t2.start();

    }
}

运行结果:


2、设置线程的优先级setPriority()

查看API文档我们知道:

public final void setPriority(int newPriority)

更改此线程的优先级。

首先调用这个线程的checkAccess方法,没有参数。 这可能会导致投掷SecurityException

否则,该线程的优先级设置为指定的小newPriority和最大允许的线程的线程组的优先级。

参数

newPriority - 设置此线程的优先级

异常

IllegalArgumentException - 如果优先级不在 MIN_PRIORITYMAX_PRIORITY

SecurityException - 如果当前线程不能修改此线程。

参考代码1:

public class ThreadPriorityDemo {
    public static void main(String[] args) {
        //创建2个或者2个以上的线程对象
        MyPriorityThread t1 = new MyPriorityThread();
        MyPriorityThread t2 = new MyPriorityThread();
        t1.setName("张学友");
        t2.setName("郭富城");

        //设置线程的优先级
        t1.setPriority(1000);
        t2.setPriority(0);

        t1.start();
        t2.start();

    }
}

 运行结果:

如果优先级不在 MIN_PRIORITYMAX_PRIORITY

查看原码:

 通过查看原码我们发现,优先级最小只能给1,最大只能给10

 参考代码2:

public class ThreadPriorityDemo {
    public static void main(String[] args) {
        //创建2个或者2个以上的线程对象
        MyPriorityThread t1 = new MyPriorityThread();
        MyPriorityThread t2 = new MyPriorityThread();
        t1.setName("张学友");
        t2.setName("郭富城");

        //设置线程的优先级
        t1.setPriority(10);
        t2.setPriority(1);

        t1.start();
        t2.start();

    }
}

运行结果:


🉑到底啦 !给靓仔一个关注吧!🉑

请关注下一篇文章 多线程2.0

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

liangzai2048

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

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

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

打赏作者

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

抵扣说明:

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

余额充值