Java 多线程和线程池使用

java多线程实现的几种方法

1.继承Thread类

继承Thread类,重写run方法,创建线程类对象调用start方法启动线程。

public class ThreadDemo {

    /**
* 继承Thread类创建线程
*/
public static class MyThread extends Thread { public MyThread() {
} public MyThread(String threadName) {
this.setName(threadName);
} @Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("继承Thread类创建线程:" + this.getName() + " " + i);
}
}
} public static void main(String[] args) {
MyThread myThread1 = new MyThread("thread1");
MyThread myThread2 = new MyThread("thread2");
MyThread myThread3 = new MyThread("thread3");
myThread1.start();
myThread2.start();
myThread3.start();
} }

程序三次运行结果(部分):



从以上运行结果可以看出线程的执行顺序是随机性的和代码的编写顺序无关。

2.实现Runnable接口

Runnable接口中只有一个抽象run方法。

实现Runnable接口重写run方法创建自己的线程,调用start方法启动线程。

public class ThreadDemo {

    /**
* 实现Runnable创建线程
*/
public static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("实现Runnable创建线程:" + Thread.currentThread().getName() + " " + i);
}
}
} public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
Thread thread3 = new Thread(myRunnable);
thread1.setName("thread1");
thread2.setName("thread2");
thread3.setName("thread3");
thread1.start();
thread2.start();
thread3.start();
} }

利用构造函数Thread(Runnable target) 启动线程,实际上Thread类也实现了Runnable接口,这样就可以将Thread对象交由其他线程调用run方法执行。

3.实现Callable接口

Runnable是出自jdk1.0,Callable出自jdk1.5,Callable相当于是对Runnable接口的增强,Runnable和Callable接口的区别就在于Callable接口有一个带有返回值的call方法,并且可以抛出异常

public class ThreadDemo {

    /**
* 实现Callable创建线程
*/
public static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("实现Callable创建线程:" + Thread.currentThread().getName() + " " + i);
}
return "线程执行完成";
}
} public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
long start = System.currentTimeMillis();
thread.start();
String result = futureTask.get();
System.out.printf("线程执行结果【%s】,耗时:%d ms", result, System.currentTimeMillis() - start);
} }

实现Callable接口,重写call方法,通过FutureTask交给线程执行,阻塞等待结果返回。

callable逻辑交由异步线程处理,主线程通过阻塞接受异步线程返回内容。

以下是根据JAVA FutureTask类源码注释翻译的内容:

FutureTask:一个可取消的异步的计算,这个类实现了runnable和Future接口。future提供了开始,取消计算,查询计算是否完成。get()方法将会是堵塞的,调用get()方法将会堵塞直到计算完成返回结果。一旦计算完成了就不可以再次开始或者取消,除非是调用runAndReset()方法。

FutureTask有NEW,COMPLETING,NORMAL,EXCEPTIONAL,CANCELLED,INTERRUPTINGINTERRUPTED七种状态,创建时状态为NEW,结果未赋值时更新为COMPLETING,结果赋值后更新为NORMAL,发生异常后会将状态置为EXCEPTIONAL,调用cancel方法后更新为CANCELLED或者INTERRUPTING状态。

Java线程池的使用

线程池的优点

  1. 线程池能够最大化资源的利用率,因为它可以重复利用已经创建的线程,避免了每次创建和销毁线程时的开销。

  2. 线程池可以根据需要动态地创建和销毁线程,从而更好地管理和调度线程,提高程序的性能和响应速度。

  3. 线程池可以更有效地处理大量并发请求,因为它可以将任务拆分成较小的部分,并发地提交给线程池中的线程处理,从而更快地完成任务。

  4. 线程池可以减少锁竞争等线程间相互干扰的问题,提高了程序的正确性和可靠性。

  5. 线程池可以提高程序的安全性,因为它可以限制同一时间只有一个线程可以执行某个任务,减少了多个线程同时执行同一个任务时可能发生的错误。

池化技术基本都具有提高程序的性能、响应速度、资源利用率、方便统一管理的优点

Java线程池

ThreadPoolExecutor 线程池位于 java.util.concurrent 包下,是 Java 中用于实现线程池的一种基础类。

Executor接口继承关系

  • Executor线程池相关顶级接口,它将任务的提交与任务的执行分离开来。

  • ExecutorService继承并扩展了Executor接口,提供了Runnable、FutureTask等主要线程实现接口扩展。

  • ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。

  • ScheduledExecutorService接口,是延时执行类任务的主要实现。

ThreadPoolExecutor构造方法参数详解

以下是ThreadPoolExecutor包含所有参数的构造方法:

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
  • corePoolSize:线程池中核心线程的数量。

    线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。任务提交到线程池后,首先会检查当前线程数是否达到了corePoolSize,如果没有达到的话,则会创建一个新线程来处理这个任务。

  • maximumPoolSize:线程池中最大线程数量。

    当前线程数达到corePoolSize后,如果继续有任务被提交到线程池,会将任务缓存到工作队列中。如果队列也已满,则会去创建一个新线程来出来这个处理。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。

  • keepAliveTime:线程池中空闲线程的存活时间。

    一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么超过keepAliveTime设定的时间后,这个空闲线程会被销毁。

  • unit:空闲线程的存活时间单位。

    java.util.concurrent.TimeUnit 枚举类。

  • workQueue:工作队列。

    存放工作任务的队列,任务调度时会从该队列取出任务。JDK提供了四种实现队列:

    1. ArrayBlockingQueue

    其是一个基于数组的阻塞队列,底层使用数组进行元素的存储。创建该阻塞队列实例需要指定队列容量,故其是一个有界队列。在并发控制层面,无论是入队还是出队操作,均使用同一个ReentrantLock可重入锁进行控制,换言之生产者线程与消费者线程间无法同时操作。

    1. LinkedBlockingQuene

    其是一个基于链表的阻塞队列,底层使用链表进行元素的存储。该阻塞队列容量默认为 Integer.MAX_VALUE,即如果未显式设置队列容量时可以视为是一个无界队列;反之构建实例过程中指定队列容量,则其就是一个有界队列。在并发控制层面,其使用了两个ReentrantLock可重入锁来分别控制对入队、出队这两种类型的操作。使得生产者线程与消费者线程间可以同时操作提高效率。

    1. SynchronousQuene

    其是一个同步队列。特别地是由于该队列没有容量无法存储元素,故生产者添加的数据会直接被消费者获取并且立刻消费。所以当生产者线程添加数据时,如果此时恰好有一个消费者已经准备好获取队头元素了,则会添加成功;否则要么添加失败返回false要么被阻塞。如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

    1. PriorityBlockingQueue

    线程安全版本的优先级队列PriorityBlockingQueue,其是一个支持优先级的无界阻塞队列。底层使用数组实现元素的存储、最小堆的表示。默认使用元素的自然排序,即要求元素实现Comparable接口;或者显式指定比较器Comparator。在并发控制层面,无论是入队还是出队操作,均使用同一个ReentrantLock可重入锁进行控制。值得一提的是,在创建该队列实例时虽然可以指定容量。但这并不是队列的最终容量,而只是该队列实例的初始容量。一旦后续过程队列容量不足,其会自动进行扩容。值得一提的是,为了保证同时只有一个线程进行扩容,其内部是通过CAS方式来实现的,而不是利用ReentrantLock可重入锁来控制。故PriorityBlockingQueue是一个无界队列。

    以上队列相关描述取自:Java多线程之阻塞队列(知乎文章)

  • threadFactory:线程工厂。

    创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程。

  • handler:拒绝策略。

    当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,就会执行相应的拒绝策略。jdk中提供了4中拒绝策略:

    1. CallerRunsPolicy

    只要线程池没有关闭,就交由调用方线程运行。谁提交任务谁来执行这个任务,即将任务执行放在提交的线程里面,减缓了线程的提交速度,相当于负反馈。在提交任务线程执行任务期间,线程池又可以执行完部分任务,从而腾出空间来。

    使用场景:一般不允许失败的、对性能要求不高、并发量较小的场景下使用。

    1. AbortPolicy

    直接丢弃任务,抛出RejectedExecutionException异常。

    1. DiscardPolicy

    直接丢弃任务,什么都不做。

    1. DiscardOldestPolicy

    弃任务队列中等待事件最长的,即最老的任务。

线程和线程池的使用规范

java提供了几种常见的线程池创建方式:

  • FixThreadPool 可重用固定线程池

    线程池的大小一旦达到设定数量就会保持不变。

  • SingleThreadExcutor 单线程化的线程池

    只有一个线程的线程池,任务按照提交的次序顺序执行的。

  • CachedThreadPool 可缓存线程池

    此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。线程池的线程数可达到Integer.MAX_VALUE,即2147483647。

具体实现可自行查看JDK源码。

注意

  1. 对于线程资源应通过线程池提供,避免自行显示创建线程。

  2. 虽然java提供了以上三种常见的线程池创建方式,但是以上三种队列,队列长度或者线程数的最大限制可达到Integer.MAX_VALUE,可能导致内存溢出,所以线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor的方式。

最新版阿里巴巴泰山版《Java开发手册》对线程和线程池的使用也提出了强制类型要求,如下图:

java线程池和多线程的使用详解的更多相关文章

  1. java 线程池、多线程实战(生产者消费者模型,1 vs 10) 附案例源码

    导读 前二天写了一篇<Java 多线程并发编程>点我直达,放国庆,在家闲着没事,继续写剩下的东西,开干! 线程池 为什么要使用线程池 例如web服务器.数据库服务器.文件服务器或邮件服务器 ...

  2. Java线程同步的四种方式详解(建议收藏)

    ​ Java线程同步属于Java多线程与并发编程的核心点,需要重点掌握,下面我就来详解Java线程同步的4种主要的实现方式@mikechen 目录 什么是线程同步 线程同步的几种方式 1.使用sync ...

  3. 线程池的使用(ThreadPoolExecutor详解)

    为什么要使用线程池? 线程是一个操作系统概念.操作系统负责这个线程的创建.挂起.运行.阻塞和终结操作.而操作系统创建线程.切换线程状态.终结线程都要进行CPU调度——这是一个耗费时间和系统资源的事情. ...

  4. java线程的五大状态,阻塞状态详解

    一.状态简介 一个线程的生命周期里有五大状态,分别是: 新生 就绪 运行 死亡 运行后可能遇到的阻塞状态 二.相关方法 2.1 新生状态 Thread t = new Thread(); 正如我们前面 ...

  5. 线程池ThreadPoolExecutor、Executors参数详解与源代码分析

    欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1. ThreadPoolExecutor数据成员 Private final Atom ...

  6. java线程池开启多线程

    // //maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常 ThreadPoolExecutor pool = new ThreadPoolExecutor( ...

  7. JAVA 线程池, 多线程

    http://tutorials.jenkov.com/java-util-concurrent/executorservice.html http://howtodoinjava.com/core- ...

  8. java线程中yield(),sleep(),wait()区别详解

    1.sleep() 使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁.也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据.注意该方 ...

  9. java线程并发控制:ReentrantLock Condition使用详解

    本文摘自:http://outofmemory.cn/java/java.util.concurrent/lock-reentrantlock-condition java的java.util.con ...

  10. Java线程池详解(二)

    一.前言 在总结了线程池的一些原理及实现细节之后,产出了一篇文章:Java线程池详解(一),后面的(一)是在本文出现之后加上的,而本文就成了(二).因为在写完第一篇关于java线程池的文章之后,越发觉 ...

随机推荐

  1. Spring 常见问题 - 1

    1. 什么是spring? 两大功能,依赖注入和面向切面编程(DI & AOP) 为了降低 Java 开发的复杂性,Spring 采取了以下4种关键策略 基于 POJO 的轻量级和最小侵入性编 ...

  2. 把OSC_IN/OSC_OUT引脚作为GPIO端口PD0/PD1

    外部振荡器引脚OSC_IN/OSC_OUT可以用做GPIO的PD0/PD1,通过设置复用重映射和调试I/O配置寄存器(AFIO_MAPR)实现.这个重映射只适用于36. 48和64脚的封装(100脚和 ...

  3. RPA主流厂商有哪些?

    RPA(机器人流程自动化(Robotic Process Automation)是一种能够自动化基于规则.结构化和重复的业务流程的技术.机器人流程自动化降低了成本,同时防止了人为错误,该技术目前已应用 ...

  4. J - Straight Master Gym - 101775J 差分

    题意:纸牌顺子:连续的3张或连续的4张或连续的5张为顺子.手中的牌共有n个数字,每个数字是a[i]个,能不能把手中所有的牌都是属于顺子. 1 ≤ T ≤ 100. 1 ≤ N ≤ 2 × 105. 0 ...

  5. conda环境下使用nvcc -V报错nvcc: command not found的一种解决方法

    前言 缘起  实验室的学弟问我为什么他使用nvcc命令报错,起先我以为他用的是老师给的root账户,按照参考文献1便可以解决问题.  但由于并非root用户,/usr/local下没有cuda,于是便 ...

  6. Centos7端口开放及查看

    1.开放端口 firewall-cmd --zone=public --add-port=端口/tcp --permanent eg:firewall-cmd --zone=public --add- ...

  7. RMQ总结

    题目描述 给定N个数的序列和M次询问,每次询问给定左右端点区间中的最大值 输入样例: 6 (N) 34 1 8 123 3 2 4 (M) 1 2 1 5 3 4 2 3 输出样例: 34 123 1 ...

  8. 每日复习——static , 饿汉式方法,懒汉式方法,以及单例设计模式

    1.1.static 的使用 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过 new 关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部 ...

  9. [Git/GitLab]使用SSH远程登录GitLab/GitHub

    1 前言 近日,换了台新电脑. 今日,正要更新(git pull)GitLab的源码时,在配置(用户名,邮箱,密码git config --global -l)完全无误的情况下,却报出如下错误: $ ...

  10. [Windows/Linux]判别服务器: 虚拟机 | 物理机 ?

    物理主机,一般称: [宿主机] 虚拟机信息,一般涉及如下关键词: VMware : VMware 虚拟化技术 Vistualbox KVM(Kernel-based Virtual Machine): ...