【Java|多线程与高并发】详解start()方法和run()方法的区别

简介: 本篇文章主要讲解以下几个问题:start()方法和run()方法的区别与联系 为什么不能连续两次调用shart()方法 run()方法中可能忽略的问题

前言

本篇文章主要讲解以下几个问题:

start()方法和run()方法的区别与联系

为什么不能连续两次调用shart()方法

run()方法中可能忽略的问题


1.start()方法和run()方法


我们在创建线程时,会重写run()方法.run()方法可以理解为线程要做的任务,但是直接调用run()方法,只是main线程也就是主线程去执行的,是没有新线程产生的

如果要想让线程去执行run()方法里面的代码,就需要让创建线程的对象去调用start()方法,shart()方法可以创建并启动线程,JVM调用run()方法后(后面会有介绍) 线程才会去执行


2.不能两次调用start()方法


同一个线程对象,只能调用一次start()方法,不能两次调用start()方法,调用两次的话,会抛出 IllegalThreadStateException 这个异常

示例如下:

public class Example6 {

   public static void main(String[] args) {

       Thread t1 = new Thread(()->{

           System.out.println("1");

       });

       t1.start();

       t1.start();

   }

}

111.png

为什么会这样?我们可以查看start()的源码

112.png

上面有一段话: A zero status value corresponds to state "NEW" 意思是 零状态值对应于状态“NEW”

线程运行时有六种状态,先熟悉一下,之后还会写文章进行详细介绍:

状态 描述

新建(NEW) 表示线程已经创建好了,但是还没有调用start()方法

就绪(RUNNABLE) 表示线程可能在运行,也可能在就绪队列

阻塞 (BLOCKED) 表示线程处于等在锁的状态

等待(WAITING) 表示线程处于条件等待状态,当触发条件后会唤醒

计时等待(TIME_WAIT) 比WAITING多了个超时条件触发的机制

终止(TERMINATED) 表示线程执行结束

因此线程再调用start()方法之后,可能处于终止,或者其它非NEW状态,第二次调用的时候,相当于重新让线程运行一遍,从线程安全和线程本身的执行逻辑来看,都是不合理的,因此在调用start()方法之后,会对线程的状态进行一个判断,如果线程不是在NEW状态下,就会抛出异常


3.线程的执行是随机的


线程的执行是随机的,这也是个老生常谈的问腿了,究其原因还是因为线程的"抢占式执行",谁先"抢"到

操作系统分配的CPU资源,谁先去执行


start()方法和run()方法的执行顺序不一定相同


示例1:

看一下代码及代码运行结果

class MyThread extends Thread{

   private int val;

   public MyThread(int val) {

       this.val = val;

   }

   @Override

   public void run() {

       System.out.println(val);

   }

}

public class Example1 {

   public static void main(String[] args) {

       MyThread myThread1 = new MyThread(1);

       MyThread myThread2 = new MyThread(2);

       MyThread myThread3 = new MyThread(3);

       MyThread myThread4 = new MyThread(4);

       MyThread myThread5 = new MyThread(5);

       myThread1.start();

       myThread2.start();

       myThread3.start();

       myThread4.start();

       myThread5.start();

   }

}

114.png

由此我们可以看到虽然是myThread1先调用的start()方法,但是输出的结果却是在第二位,而myThread3后执行却在第一位

115.png

再次运行一次代码的执行结果虽然与上次不同,但仍然是随机的,当然也不是没有运行结果是1 2 3 4 5的可能


示例2:

看一下代码及代码运行结果

public class Example4 {

   public static void main(String[] args) {

       Thread t1 = new Thread(()->{

           while (true){

               System.out.println("t1");

           }

       });

       Thread t2 = new Thread(()->{

           while (true){

               System.out.println("t2");

           }

       });

       t1.start();

       t2.start();

   }

}

117.png

这里我们也能看到,start()执行的顺序与run()方法执行的顺序无关,随机执行t1和t2的run()方法 虽然这里是while(true),但是线程之间是相互独立的,所以并不影响

因此可以得出结论:start()方法和run()方法的执行顺序不一定相同


4.run()方法由JVM调用


start()执行完后,创建的新线程不会立刻去执行run()方法, run()方法会先被JVM去调用,之后对应的线程才会去执行.


public Thread(Runnable target)中的target

之前再讲创建线程的5种方法,介绍了实现Runnable接口,创建线程的方法

117.png

但其实这里面有一个坑,那就是public Thread(Runnable target)中的target参数,来看一下run()方法的底层源码

118.png

如果这里面的target不为空,才会去执行target的run()方法.如果传一个null,就不会去执行,如图所示:

120.png

代码如下:

class MyRunnable implements Runnable{

   @Override

   public void run() {

       System.out.println("1");

   }

}

public class Example7 {

   public static void main(String[] args) {

       MyRunnable myRunnable1 = new MyRunnable();

       MyRunnable myRunnable2 = null;

       Thread t1 = new Thread(myRunnable1);

       t1.start();

       Thread t2 = new Thread(myRunnable2);

       t2.start();

   }

}

因此要注意使用继承Runnable接口 创建线程的时候 要注意target不要为null

相关文章
|
2月前
|
安全 Java 开发者
Java中WAIT和NOTIFY方法必须在同步块中调用的原因
在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
46 4
|
2月前
|
Java
深入探讨Java中的中断机制:INTERRUPTED和ISINTERRUPTED方法详解
在Java多线程编程中,中断机制是协调线程行为的重要手段。了解和正确使用中断机制对于编写高效、可靠的并发程序至关重要。本文将深入探讨Java中的`Thread.interrupted()`和`Thread.isInterrupted()`方法的区别及其应用场景。
52 4
|
2月前
|
Java 数据处理 数据安全/隐私保护
Java处理数据接口方法
Java处理数据接口方法
27 1
|
10天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
34 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
62 1
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
40 3
|
3月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
28 2
|
3月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
44 2
|
3月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
49 1
|
3月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
57 1