当前位置: 首页 > news >正文

Java SE线程的创建

线程的创建

方式一:继承Thread类

1.1 步骤:

  1. 创建一个子类继承Thread类;
  2. 重写Thread类的run()方法,在该方法中编写该线程要执行的操作;
  3. 创建当前子类的对象;
  4. 通过创建的子类对象调用start()方法,启动线程。

例如:
创建一个线程用于打印1到100间的偶数。

//1.创建一个子类继承Thread类;
class PrintNumber extends Thread {//2. 重写Thread类的run()方法,在该方法中编写该线程要执行的操作;@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}
}

主类调用:

public class EvenNumberTest extends Thread {public static void main(String[] args) {//3. 在主类的main方法中,创建当前子类的对象;PrintNumber printNumber = new PrintNumber();//4. 通过创建的子类对象调用start()方法,启动线程。printNumber.start();/** 能否使用printNumber.run()替换printNumber.start()的调用,实现分线程的创建和调用? 不能!* start() 方法的作用是启动一个新的线程,并在该线程中执行 run() 方法。* 这是真正意义上的多线程执行。* 如果调用的是 printNumber.run(),则 run() 方法会在当前线程(即主线程)中执行,并不会创建新的线程,也就无法实现并发执行的效果。* */
//        printNumber.run();/** 拓展:再提供一个分线程,用于100以内偶数的遍历。** 注意:不能让已经start()的线程,再次执行start(),否则报异常IllegalThreadStateException* * *///此时需要新建一个对象,而不能沿用之前创建的对象PrintNumber printNumber1 = new PrintNumber();printNumber1.start();}
}

练习: 创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数。
解决思路: 创建两个类均继承Thread类,分别打印偶数和奇数。
偶数线程类:

class EvenNumber extends Thread {@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}
}

奇数线程类:

class OddNumber extends Thread {@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 != 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}
}

主线程调用:
写法一:

public class Test {public static void main(String[] args) {EvenNumber evenNumber = new EvenNumber();OddNumber oddNumber = new OddNumber();evenNumber.setName("偶数线程");oddNumber.setName("奇数线程");evenNumber.start();oddNumber.start();}
}

写法二:

public class Test {public static void main(String[] args) {Thread tEven= new Thread(){@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}};Thread tOdd = new Thread(){@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 != 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}};tEven.setName("偶数线程");tEven.start();tOdd.setName("奇数线程");tOdd.start();
}

写法三:

public class Test {public static void main(String[] args) {//使用匿名对象创建线程new Thread(){@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}}.start();new Thread(){@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 != 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}}.start();}
}

写法四:

	public class Test {public static void main(String[] args) {// 使用Lambda表达式创建线程new Thread(() -> {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}, "偶数线程").start();new Thread(() -> {for (int i = 1; i <= 100; i++) {if (i % 2 != 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}, "奇数线程").start();}
}

方式二:实现Runnable接口

2.1步骤:

  1. 创建一个实现Runnable接口的类;
  2. 重写该接口的run()方法,将此线程要执行的操作,声明在此方法体中;
  3. 创建当前实现类的对象;
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例;
  5. Thread类的实例调用start():1.启动线程;2.调用当前线程的run()方法。
    例如:
    创建一个线程用于打印1到100间的偶数。
// 1. 创建一个实现Runnable接口的类;
public class MyRunnable implements Runnable{// 2. 重写该接口的run()方法,将此线程要执行的操作,声明在此方法体中;@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}public static void main(String[] args) {//3. 创建当前实现类的对象;MyRunnable mr = new MyRunnable();//4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例;Thread t1 = new Thread(mr);//Thread类的实例调用start()。t1.start();/** 拓展:再创建一个线程,用于遍历100以内的偶数** *///此时可以发现,我们无需再创建新的对象,而是使用之前创建的对象即可Thread t2 = new Thread(p);t2.start();}
}

练习: 创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数。
使用匿名内部类对象来实现线程的创建和启动
方式一:

//其实还是上面的写法三的创建形式,只不过在创建时设置其name属性
public class Test {public static void main(String[] args) {new Thread("偶数线程") {@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}}.start();new Thread("奇数线程") {@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 != 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}}.start();}
}

方式二:

public class Test {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}}, "偶数线程").start();new Thread(new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 100; i++) {if (i % 2 != 0) {System.out.println(Thread.currentThread().getName() + "  " + i);}}}}, "奇数线程").start();}
}

两种方式的区别与联系

区别

  • 继承Thread:线程代码存放Thread子类run方法中。
  • 实现Runnable:线程代码存在接口的子类的run方法。

实现Runnable接口比继承Thread类所具有的优势

  • 避免了单继承的局限性;
  • 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源;
  • 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

联系

  1. 方式一的Thread类本身就实现了Runnable接口,即:
public class Thread extends Object implements Runnable
  1. 方式二的Runnable接口的实现类可以作为Thread类的构造器参数;
  2. 方式二的Runnable接口的实现类可以被多个Thread类的实例共享。

值得注意的是,第二种方式在执行start()方法时,执行逻辑与过程是这样的:
首先我们实现runnable接口时重写了run()方法;
start()方法调用的是Thread类中的start()方法,该方法调用了Thread类自己的run()方法,而Thread中的run()方法是这样的:

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

它先对target是否为空进行了判断,那么这个target是什么呢,它是

private Runnable target;

Runnable接口类型的一个成员变量,而我们new的Thread类的构造器是这个:

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

而这个构造器就把target属性赋值了,该init函数调用的是:

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

继续查看调用的init函数:

private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name;Thread parent = currentThread();SecurityManager security = System.getSecurityManager();if (g == null) {/* Determine if it's an applet or not *//* If there is a security manager, ask the security managerwhat to do. */if (security != null) {g = security.getThreadGroup();}/* If the security doesn't have a strong opinion of the matteruse the parent thread group. */if (g == null) {g = parent.getThreadGroup();}}/* checkAccess regardless of whether or not threadgroup isexplicitly passed in. */g.checkAccess();/** Do we have the required permissions?*/if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g;this.daemon = parent.isDaemon();this.priority = parent.getPriority();if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;setPriority(priority);if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;/* Set thread ID */tid = nextThreadID();}

可以看到在从下往上数第十行的位置,对target进行了赋值操作。
实现Runnable接口时的调用过程

方式三:实现callable接口

3.1 步骤:

  1. 创建一个实现Callable的实现类;
  2. 实现call方法,将此线程需要执行的操作声明在call()中;
  3. 创建Callable接口实现类的对象;
  4. 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象;
  5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start();
  6. 获取Callable中call方法的返回值。

示例: 打印1到100的偶数,并对其求和;

//1.创建一个实现Callable的实现类
class NumThread implements Callable {//2.实现call方法,将此线程需要执行的操作声明在call()中@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {if (i % 2 == 0) {System.out.println(i);sum += i;}
//            Thread.sleep(1000);}return sum;}
}

主测试类中:

public class CallableTest {public static void main(String[] args) {//3.创建Callable接口实现类的对象NumThread numThread = new NumThread();//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象FutureTask futureTask = new FutureTask(numThread);//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()Thread t1 = new Thread(futureTask);t1.start();//      接收返回值try {//6.获取Callable中call方法的返回值//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。Object sum = futureTask.get();System.out.println("总和为:" + sum);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}

对比Runnable接口

  • 与使用Runnable相比, Callable功能更强大些
    • 相比run()方法,可以有返回值
    • 方法可以抛出异常
    • 支持泛型的返回值(需要借助FutureTask类,获取返回结果)
  • Future接口(简单了解)
    • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
    • FutureTask是Futrue接口的唯一的实现类
    • FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
  • 缺点:在获取分线程执行结果的时候,当前线程(或是主线程)受阻塞,效率较低。

方式四:使用线程池创建线程

构建一个新的线程是有代价的,因为涉及与操作系统的交互。如果程序中创建了大量的生命周期很短的线程,就应该使用线程池。一个线程池中包含多个准备运行的空闲线程。将Runnable对象交给线程池,就会有一个线程调用run()方法,当run()方法退出时,线程不会死亡,而是在池中准备为下一个请求提供服务。
另一个使用线程池的理由是减少并发线程的数量。创建大量线程会大大降低性能甚至使虚拟机崩溃。如果有一个会创建许多线程的算法,应该使用一个线程数“固定的“线程池来限制并发线程的总数。
好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

从JDK5.0开始,Java内置线程池相关的API。在java.util.concurrent包下提供了线程池相关API:ExecutorServiceExecutors

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor。
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行Callable
    • void shutdown() :关闭连接池
  • Executors:一个线程池的工厂类,通过此类的静态工厂方法可以创建多种类型的线程池对象。
    • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    • Executors.newFixedThreadPool(int nThreads); 创建一个可重用固定线程数的线程池
    • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(int corePoolSize):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

可以采用实现 Runnable 接口 或 Callable接口的形式来使用线程池创建线程。

4.1 步骤:

  1. 创建一个实现 Runnable 接口 或 Callable接口的实现类;
  2. 重写该接口的run()方法,将此线程要执行的操作,声明在此方法体中;
  3. 提供指定线程数量的线程池;
  4. 执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象;
  5. 关闭连接池。

示例: 使用两个线程分别打印1到100内的奇数和偶数,并对其求和。

//1). 创建一个实现 Runnable 接口的实现类;
class NumberThread implements Runnable{//2). 重写该接口的run()方法,将此线程要执行的操作,声明在此方法体中;@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}
//1). 创建一个实现 Runnable 接口的实现类;
class NumberThread1 implements Runnable{//2). 重写该接口的run()方法,将此线程要执行的操作,声明在此方法体中;@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}
//1). 创建一个实现 Callable 接口的实现类;
class NumberThread2 implements Callable {
//2). 重写call()方法,在该方法中编写该线程要执行的操作;@Overridepublic Object call() throws Exception {int evenSum = 0;//记录偶数的和for(int i = 0;i <= 100;i++){if(i % 2 == 0){evenSum += i;}}return evenSum;}}

主类中:

public class ThreadPoolTest {public static void main(String[] args) {//3).提供指定线程数量的线程池ExecutorService service = Executors.newFixedThreadPool(10);ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//        //设置线程池的属性
//        System.out.println(service.getClass());//ThreadPoolExecutorservice1.setMaximumPoolSize(50); //设置线程池中线程数的上限//4).执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread());//适合适用于Runnableservice.execute(new NumberThread1());//适合适用于Runnabletry {Future future = service.submit(new NumberThread2());//适合使用于CallableSystem.out.println("总和为:" + future.get());} catch (Exception e) {e.printStackTrace();}//5).关闭连接池service.shutdown();}}

与此同时,Java 提供了 java.util.concurrent.Executors 工具类来快速创建常见的线程池类型:

1. 固定大小线程池:newFixedThreadPool

ExecutorService executor = Executors.newFixedThreadPool(10);
  • 核心线程数 = 最大线程数 = 10
  • 使用无界队列 LinkedBlockingQueue,所有线程一直存活,适合负载较重、任务量稳定的场景

2. 缓存线程池:newCachedThreadPool

ExecutorService executor = Executors.newCachedThreadPool();
  • 核心线程数 = 0
  • 最大线程数 = Integer.MAX_VALUE
  • 空闲线程存活时间为 60 秒
  • 使用 SynchronousQueue,适用于大量短生命周期的任务

3. 单线程池:newSingleThreadExecutor

ExecutorService executor = Executors.newSingleThreadExecutor();
  • 只有一个线程,确保任务串行执行
  • 使用无界队列
  • 保证顺序执行且不会有并发问题

4. 调度线程池:newScheduledThreadPool

ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
  • 支持定时任务和周期性任务执行
  • 例如延迟执行或固定频率重复执行

手动创建线程池

虽然 Executors 提供了快捷方式,但在生产环境中更推荐使用 ThreadPoolExecutor 构造函数手动创建线程池,以便更灵活地配置参数。
ThreadPoolExecutor构造函数签名:

ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

参数说明:

参数名含义
corePoolSize核心线程数(常驻线程数量)
maximumPoolSize最大线程数(允许的最大线程数量)
keepAliveTime非核心线程空闲超时时间
unit超时时间单位
workQueue存放任务的阻塞队列
threadFactory线程工厂,用于创建线程(可自定义命名等)
handler拒绝策略,当任务无法提交时的处理方式

示例代码:

ThreadPoolExecutor executor = new ThreadPoolExecutor(5, // corePoolSize10, // maximumPoolSize60, // keepAliveTimeTimeUnit.SECONDS,new LinkedBlockingQueue<>(100), // 有界队列new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

线程池的工作流程

  • 提交任务后:
    • 若当前运行线程 < corePoolSize,则创建新线程处理任务。
    • 否则,尝试将任务放入工作队列。
  • 如果队列满:
    • 若当前线程数 < maximumPoolSize,创建新线程处理任务。
    • 否则,执行拒绝策略(如抛异常、调用者执行等)。

常见拒绝策略(RejectedExecutionHandler)

策略行为
AbortPolicy默认策略,抛出 RejectedExecutionException 异常
CallerRunsPolicy由提交任务的线程自己执行该任务
DiscardPolicy默默丢弃任务,不抛异常
DiscardOldestPolicy丢弃队列中最老的任务,然后尝试重新提交新任务

使用线程池的最佳实践

实践建议说明
明确任务性质CPU密集型 vs IO密集型,决定线程池大小
避免无界队列如 LinkedBlockingQueue 不传容量会导致任务堆积
设置合适的拒绝策略避免系统崩溃,提供降级处理
自定义线程工厂方便调试和日志追踪(如命名线程)
关闭线程池使用 shutdown() 或 shutdownNow() 避免资源泄漏

示例代码:完整使用线程池

import java.util.concurrent.*;public class ThreadPoolDemo {public static void main(String[] args) {ThreadPoolExecutor executor = new ThreadPoolExecutor(2,4,60,TimeUnit.SECONDS,new LinkedBlockingQueue<>(10),new ThreadPoolExecutor.CallerRunsPolicy());for (int i = 0; i < 20; i++) {final int taskNo = i;executor.execute(() -> {System.out.println("正在执行任务 " + taskNo + ",线程:" + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟任务耗时} catch (InterruptedException e) {e.printStackTrace();}});}executor.shutdown(); // 关闭线程池}
}

以上就是四种创建线程的方法,其间如果涉及多线程对共享资源的处理,还需要使用同步代码块或同步方法以及使用wait()、notify()/notifyAll()函数来完成进程间通信从而解决多线程对共享资源的使用导致的并发安全问题。

http://www.lryc.cn/news/579873.html

相关文章:

  • 怎么处理[TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark
  • 通道密度与安全性的突破:SiLM5768LCG-DG 六通道互锁隔离器如何重构高可靠系统?
  • Unity HDRP + Azure IoT 的 Python 后端实现与集成方案
  • 使用assembly解决jar包超大,实现依赖包、前端资源外置部署
  • linux 系统已经部署并正常提供服务的docker存储目录迁移
  • 【Prometheus 】通过 Pushgateway 上报指标数据
  • 每天一个前端小知识 Day 21 - 浏览器兼容性与 Polyfill 策略
  • AI+Web3:从Web2到Web3的范式革命与深度技术实践
  • 开源项目XYZ.ESB:数据库到数据库(DB->DB)集成
  • lsblk 显示磁盘(如 /dev/sda)已变大,但分区(如 /dev/sda2)未变,则需要手动调整
  • 微服务架构的演进:迈向云原生
  • 【C++】访问者模式中的双重分派机制详解
  • 【效率提升教程】飞书自动化上传图片和文字
  • jQuery Mobile 安装使用教程
  • 《新消费模式与消费者权益保护研讨会》课题研讨会在北京顺利召开
  • 【嵌入式ARM汇编基础】-ELF文件格式内部结构详解(四)
  • 状态机管家:MeScroll 的交互秩序维护
  • 智能电动汽车 --- 车辆网关路由缓存
  • SAP SD模块之业务功能剖析
  • 京东小程序JS API仓颉改造实践
  • 「AI产业」| 《中国信通院华为:智能体技术和应用研究报告》
  • 【加解密与C】对称加密(四) RC4
  • K8s服务发布基础
  • LiteHub中间件之限流实现
  • git教程-pycharm使用tag打标签
  • 【JavaEE】计算机工作原理
  • 【IM项目笔记】1、WebSocket协议和服务端推送Web方案
  • Angular v20版本正式发布
  • Unity 中相机大小与相机矩形大小的关系
  • Android 网络请求优化全面指南