Java多线程Thread详细讲解(万字教程)

Java如何开启线程? 方式1:继承Thread类

===========================

告诉你这个秘密,Java开启线程只有一种方式,就是Thread类的start方法!

JDK看似提供了很多和多线程相关的类,可实际上有且仅有Thread类能通过start0()方法向操作系统申请线程资源(本地方法)。

下面我们演示一下如何用Java开启一个线程。

public class MyThread extends Thread{

@Override

public void run() {

System.out.println(Thread.currentThread().getName() + “执行了!”);

}

public static void main(String[] args) {

new MyThread().start();

}

}

你也可以直接new一个Thread类,并且临时重写run方法(少写一个类)

public static void main(String[] args) {

//new MyThread().start();

new Thread(){

@Override

public void run() {

System.out.println(Thread.currentThread().getName() + “执行了!”);

}

}.start();

}

Java如何开启线程? 方式2:实现Runnable接口!(详细讲解)

===================================

这种方式的本质还是第一种,即Thread类实例调用start方法,从而去调用本地线程资源。因为有且仅有Thread类能通过start0()方法向操作系统申请线程资源(本地方法)!

​JVM对于操作系统来说就是一个程序,是一个进程,JVM进程中可以有很多个线程,我们调用start方法其实就是去申请这个线程资源。

Runnable接口:

@FunctionalInterface public interface Runnable { /** * When an object implementing interface Runnable is used * to create a thread, starting the thread causes the object’s * run method to be called in that separately executing * thread. *

* The general contract of the method run is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }

看上面的注释就知道怎么用了,当一个对象实现Runnable接口,你就去创建一个Thread,启动这个Thread,会导致run方法被调用。

我们按照官方的指导来做:

public class MyThread02 implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() + “执行了!”); } public static void main(String[] args) { new Thread(new MyThread02()).start(); } }

看到了吧,这种方式其实还是第一种。不同点在于,第一种方式是直接在Thread子类中重写的Run方法,而这种方式是你自己弄个类,里面有个run方法,传到Thread类中去,最后在Thread类启动的时候,会去调用那个类的run方法。换句话说,如果没有Thread类,这个Runnable实现类是没法自己跑起来的

所有的秘密就在Thread类的构造函数里面。

public Thread(Runnable target) { init(null, target, “Thread-” + nextThreadNum(), 0); }

调了init方法

private void init(ThreadGroup g, Runnable target, String name,long stackSize) { init(g, target, name, stackSize, null); }

又是重载,继续找:

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { //省略其他代码… this.target = target; }

原来,传入的Runnable对象是放到属性target中了。

/* What will be run. */ private Runnable target;

我们再看下Thread类中的run方法:

@Override public void run() { if (target != null) { target.run(); } }

如果是方式1,这个run方法被重写了,启动start方法的时候就直接调用那个被重写后的run方法了。现在使用方式2的话,就还是调用Thread原生的run方法,做了一个中介,又去调用target的run方法,而target是我们自己传进去的,相当于把具体run的逻辑写到外部一个类里面去了。

以上就是第二种方式的原理,下面介绍一下如何简写代码?

比如,我们可以直接把Runnable实现类用匿名的方式new出来(少些一个类):

public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + “执行了!”); } }).start(); }

因为Runnable接口只有一个抽象方法,所以还可以直接用Lamda表达式:

new Thread(() -> { System.out.println(Thread.currentThread().getName() + “执行了!”); }).start();

如果还不明白原理,可以看下这张图,安排的明明白白啦。

Java如何开启线程? 方式3:用线程池Executor

=============================

什么是线程池:  java.util.concurrent.Executors 提供了一个 java.util.concurrent.Executor 接口的实现用于创建线程池。

一个线程池包括以下四个基本组成部分:

1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池的作用:

线程池作用就是限制系统中执行线程的数量。

根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要用线程池:

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

ExecutorService

真正的线程池接口。

ScheduledExecutorService

能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。

ThreadPoolExecutor

ExecutorService的默认实现。

ScheduledThreadPoolExecutor

继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

1. newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2.newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3. newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4.newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

为了方便演示,我们就创建只有一个线程的线程池:

package com.javaxbfs.thread;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

public class MyThread03 implements Runnable{

public static void main(String[] args) throws InterruptedException {

//创建线程池

ExecutorService executorService = Executors.newSingleThreadExecutor();

//提交线程任务-Runnable对象

Future<?> future = executorService.submit(new MyThread03());

while(!future.isDone()){

Thread.sleep(200);

}

// 关闭线程池

executorService.shutdown();

System.out.println(“线程池关闭”);

}

@Override

public void run() {

System.out.println(Thread.currentThread().getName() + “========>正在执行!”);

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + “========>执行成功!”);

}

}

效果:

pool-1-thread-1========>正在执行!

pool-1-thread-1========>执行成功!

线程池关闭

这边我们使用的是Runnable对象,线程池还可以接受Callable对象。

package com.javaxbfs.thread;

import java.util.concurrent.*;

public class MyThread04 implements Callable {

public static void main(String[] args) throws InterruptedException, ExecutionException {

//创建线程池

ExecutorService executorService = Executors.newSingleThreadExecutor();

//提交线程任务-Runnable对象

Future future = executorService.submit(new MyThread04());

while(!future.isDone()){

Thread.sleep(200);

}

String result = future.get();

System.out.println(“这里拿到线程任务的返回值:” + result);

// 关闭线程池

executorService.shutdown();

System.out.println(“线程池关闭”);

}

@Override

public String call() {

System.out.println(Thread.currentThread().getName() + “========>正在执行!”);

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + “========>执行成功!”);

return “SUCCESS”;

}

}

效果:

pool-1-thread-1========>正在执行!

pool-1-thread-1========>执行成功!

这里拿到线程任务的返回值:SUCCESS

线程池关闭

两种方式我都写了例子,关于这个后面还会细说。

总结

===

1. 不管你是用上面介绍的三种方法的哪一种,都绕不开Thread类,因为只有Thread类的start方法可以去申请本地线程资源!

2. 用Runnable可以把任务代码写在Thread类外面,降低耦合度,更加灵活。

3.Callable一般配合线程池使用,重写的call方法可以添加返回值。

4.线程执行入口永远是Thread类的run方法,这个run方法要么被你重写,要么调用target的run方法,target就是实现runnable接口的对象。

封装Thread类的start方法(经典技巧)

========================

Thread类的start方法是去请求本地资源的,很多源码喜欢把这个细节封装掉,现在让我们来模仿一下这种技巧。

package com.javaxbfs.thread;

public class MyThread05 implements Runnable{

public void begin(){

new Thread(this).start();

}

@Override

public void run() {

System.out.println(Thread.currentThread().getName() + “执行了!”);

}

public static void main(String[] args) {

MyThread05 myThread05 = new MyThread05();
myThread05.begin();
}
}
思路特别简单,不需要你手动去new Thread类了,而是直接在Runnable类中添加begin方法,把这个事情给做了。

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-QJss4AFa-1714881530847)]

[外链图片转存中…(img-7BvXiwxG-1714881530848)]

[外链图片转存中…(img-IXGhtqOW-1714881530848)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值