Java多线程:常见的线程的创建方法及Thread类详解

您所在的位置:网站首页 动态库中创建线程的方法 Java多线程:常见的线程的创建方法及Thread类详解

Java多线程:常见的线程的创建方法及Thread类详解

2024-07-16 05:02| 来源: 网络整理| 查看: 265

目录

一.并发编程相关概念

▐ 线程与进程

▐ 多线程

▐ Java中线程的状态

二.线程的创建方法

▐ 方法一:继承Thread类

▐ 方法二:实现Runnable接口

▐ 其他方法

三.Thread类详解

▐ Thread常见构造方法

▐ Thread常见属性

▐ Thread常见方法

start() 与 run() 

sleep() 与 yield() 

join() 

inerrupt() 

一.并发编程相关概念 ▐ 线程与进程

线程是程序的执行流程的最小单元。一个进程(程序的执行实例)可以由一个或多个线程组成,每个线程都有自己的执行路径和执行状态。线程可以并发执行,即多个线程可以同时在不同的处理器核心或计算机上运行,从而提高程序的运行效率。

线程与进程的区别在于,进程是操作系统对一个正在运行的程序的抽象,而线程是进程内部的一个执行单位。一个进程可以有多个线程,这些线程共享进程的资源,如内存空间、文件描述符等。线程之间可以通过共享内存的方式进行通信,相比于进程间通信(如管道、消息队列)的开销更小。

▐ 多线程

对于多线程,我们可以举出这样的一个例子来帮助我们理解

一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴纳社保。 如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找 来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队, 自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。 此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别 排队执行。

 对于这样的业务场景,张三、李四和王五各自都相对于一个线程,多个线程之间相互配合才促使了整体业务流程的顺利进行,由此可见多线程对于任务处理的高效。其中由于李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread),李四和王五则为其他线程。

▐ Java中线程的状态

Java中线程的状态有以下几种:

1. 新建(New):线程被创建但还没有开始执行。

2. 就绪(Runnable):线程被调度并准备开始执行,但还没有获取CPU执行权。

3. 运行(Running):线程正在执行任务。

4. 阻塞(Blocked):当线程执行到某个阻塞操作时,如等待IO操作完成或等待某个锁的释放时,线程会进入阻塞状态。

5. 等待(Waiting):线程执行了Object类的wait()方法,或者Thread类的join()方法时,线程会进入等待状态。

6. 超时等待(Timed Waiting):线程执行了Thread类的sleep()方法或等待超时后,线程会进入超时等待状态。

7. 终止(Terminated):线程执行完任务后或者出现异常终止时,线程进入终止状态。

二.线程的创建方法

线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库)

而Java标准库中 Thread 类,便可以视为是对操作系统提供的 API 进行了进一步的抽象和封装,作为Java程序员就可以利用Thread 类来实现并发编程。

并发编程是指在计算机系统中,多个独立的任务同时进行,每个任务由一个或多个线程执行,并且这些线程可能在同一时刻同时运行。并发编程可以提高系统的执行效率和资源利用率。在并发编程中,多个线程可以同时进行不同的操作,比如读写数据、计算、网络通信等,它们可以同时执行,不需要等待其他线程的完成。常见的并发编程模型有多线程、异步编程、并行计算等。

说了这么多,归根结底还得落实到代码上,我们常见的创建线程的方式有俩种。

▐ 方法一:继承Thread类 创建一个继承自Thread类的子类。在子类中重写run()方法,定义线程的执行逻辑。在主线程中创建子类对象,并调用start()方法启动线程。 public class MyThread extends Thread { public void run() { // 线程执行逻辑 } public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } } ▐ 方法二:实现Runnable接口 创建一个实现了Runnable接口的类,并实现接口中的run()方法,定义线程的执行逻辑。在主线程中创建Runnable实例,并将其作为参数传递给Thread类的构造方法。调用Thread对象的start()方法启动线程。 public class MyRunnable implements Runnable { public void run() { // 线程执行逻辑 } public static void main(String[] args) { MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); } }

无论是继承Thread类还是实现Runnable接口,都可以创建多个线程并同时运行,以实现并发执行的效果。

▐ 其他方法

除此之外,使用匿名内部类或lambda表达式可以更快速的创建线程

匿名内部类创建Thread 子类对象

// 使用匿名类创建 Thread 子类对象 Thread t1 = new Thread() { @Override public void run() { System.out.println("使用匿名类创建 Thread 子类对象"); } };

匿名内部类创建 Runnable 子类对象

// 使用匿名类创建 Runnable 子类对象 Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("使用匿名类创建 Runnable 子类对象"); } });

 lambda 表达式创建Runnable 子类对象

// 使用 lambda 表达式创建 Runnable 子类对象 Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象")); Thread t4 = new Thread(() -> { System.out.println("使用匿名类创建 Thread 子类对象"); }); 三.Thread类详解

不管是上述创建线程中的哪一种方法,归根结底都是由 Thread 类延申开来的,Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

▐ Thread常见构造方法 方法说明Thread()创建线程对象Thread(Runnable target)使用Runnable对象创建线程对象Thread(String name)创建线程对象,并命名Thread(Runnable target, String name)使用Runnable对象创建线程对象,并命名Thread(ThreadGroup group, Runnable target)线程可以被用来分组管理,分好的组即为线程组 ▐ Thread常见属性 属性获取方法

ID

getId()名称getName()状态getState()优先级getPriority()是否后台线程isDaemon()是否存活isAlive()是否被中断isInterrupted()

其中 ID 是线程的唯一标识,不同线程不会重复,优先级高的线程理论上来说更容易被调度到,是否存活,简单的理解的话就是 run 方法是否运行结束了

▐ Thread常见方法 start() 与 run()  start()方法是Thread类中的一个方法,用于启动一个新的线程。当调用start()方法时,系统会创建一个新的线程,并在新的线程中执行run()方法的内容。start()方法会在新的线程中执行一些准备工作,然后调用run()方法。run()方法是实现了Runnable接口的类中的一个方法。在启动一个线程后,系统会自动调用该线程对象的run()方法。run()方法中包含了线程的主体代码,即线程要执行的任务。

前文中我们已经了解了如何通过重写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。重写 run 方法是提供给线程要做的事情的指令清单,线程对象可以认为是把李四、王五叫过来了,而调用start() 方法,就是喊一声:“行动起来!”,线程才真正独立去执行。

public class MyThread extends Thread { public void run() { // 线程执行逻辑 } public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }

就拿上面这段代码来说,我们能不能不管start方法直接调用线程的run方法呢?

其实是可以调用run方法的,但是直接调用run方法只会在当前线程中运行run方法的代码,不会启动新的线程去执行run方法。

而调用start方法则会启动一个新的线程,然后在新的线程中执行run方法的代码。所以,如果只调用run方法而不调用start方法,则不会创建新的线程,无法实现多线程的并发执行。

也就是说,我们可以这样做,但是这样做的话就不会实现多线程,始终我们都只能在一个线程中运行。因此在实际开发中,并不建议这样做。 

还有一点需要注意的是,对于start方法,我们只能调用一次,不能重复调用,不然会报非法线程状态异常,因为在调用start方法后,线程就已经处于Runnable状态,对于已经是Runnable状态的线程,再让它start为Runnable状态显然是不合理的。

sleep() 与 yield() 

在多线程编程中,可以使用sleep和yield方法来控制线程的执行。

sleep方法:sleep方法是Thread类提供的静态方法,可以使当前线程暂停一段时间,让其他线程有机会执行。调用sleep方法后,线程会进入阻塞状态,不会占用CPU资源。sleep方法的语法是:Thread.sleep(long millis),其中millis参数表示暂停的时间,以毫秒为单位。例如,Thread.sleep(100)表示暂停100毫秒。yield方法:yield方法是Thread类提供的静态方法,可以使当前线程让出CPU资源,使其他同优先级的线程有机会执行。调用yield方法后,线程会进入就绪状态,让出CPU资源,但并不是完全放弃CPU资源,可能会立即重新获取CPU资源。yield方法的语法是:Thread.yield()。例如,Thread.yield()表示当前线程让出CPU资源,给其他同优先级的线程执行的机会。

总的来说,sleep方法是让当前线程暂停一段时间,不会占用CPU资源,适合用于控制线程执行的时间间隔。yield方法是让当前线程主动让出CPU资源,给其他同优先级的线程执行的机会,适合用于在多个线程之间平衡负载,提高系统的性能。

join() 

join()方法是Thread类的一个方法,它用于等待该线程完成执行。具体而言,当调用一个线程的join()方法时,当前线程会被阻塞,直到该线程执行完成。

join()方法有两个重载版本:

join():等待被调用线程执行完成。join(long millis):等待被调用线程执行完成,但最多等待millis毫秒。

下面是一个例子,演示如何使用join()方法等待线程执行完成:

public class JoinExample { public static void main(String[] args) { Thread thread1 = new Thread(new MyRunnable(), "Thread 1"); Thread thread2 = new Thread(new MyRunnable(), "Thread 2"); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("All threads have finished execution."); } } class MyRunnable implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is executing."); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " has finished execution."); } }

在上面的例子中,我们创建了两个线程thread1和thread2,并将它们启动。然后,我们使用join()方法等待这两个线程执行完成。最后,当两个线程都执行完成后,才会打印"All threads have finished execution."。

inerrupt() 

在Java中,线程的interrupt()方法用于中断线程。当一个线程调用interrupt()方法时,如果目标线程当前正在执行可中断的操作(如sleep()、join()、wait()等),它将会收到一个InterruptedException异常,从而提前退出。

如果目标线程没有在可中断操作中阻塞,而是在运行中,那么调用interrupt()方法将设置目标线程的中断标志位为true。这样,目标线程可以通过检查自己的中断标志位来自行决定是否中断执行。

下面是一个示例:

public class MyThread extends Thread { public void run() { while (!Thread.currentThread().isInterrupted()) { // 执行一些操作 } System.out.println("线程被中断"); } public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); } }

在上述示例中,我们创建了一个继承自Thread类的自定义线程MyThread。在run() 方法中,我们使用了一个循环来模拟线程的执行操作。每次循环都会检查中断标志位,如果为true则退出循环并输出"线程被中断"。在前文的Thread类常见属性也说过了使用 isInterrupted() 方法就可以获取当前线程的中断标志位。

备注:和 isInterrupted() 相似的还有一个方法叫做 interrupt() 二者都能判断线程是否被打断,但是不同的点在于前者只是做出判断,并不会手动修改这个标记;而后者会在判断后手动清除打断标记,也就是置为false。

在main方法中,我们创建了一个MyThread对象并启动线程。然后通过调用Thread.sleep() 方法来让主线程睡眠1秒,最后调用thread.interrupt() 方法中断线程。当调用interrupt() 方法时,MyThread线程在下一个循环迭代时会检查到中断标志位为true,从而退出了循环并输出"线程被中断"。

 本次的分享就到此为止了,希望我的分享能给您带来帮助,创作不易也欢迎大家三连支持,你们的点赞就是博主更新最大的动力!如有不同意见,欢迎评论区积极讨论交流,让我们一起学习进步!有相关问题也可以私信博主,评论区和私信都会认真查看的,我们下次再见



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3