并发编程之线程创建到销毁、常用API
在前面一篇介绍了线程的生命周期【并发编程之多线程概念】,在本篇将正式介绍如何创建、中断线程,以及线程是如何销毁的。最后,我们会讲解一些常见的线程API。
线程创建
Java 5 以前,实现线程有两种方式:扩展java.lang.Thread类,实现java.lang.Runnable接口。这两种方式都是都是直接创建线程,而每次new Thread都会消耗比较大的资源,导致每次新建对象时性能差;而且线程缺乏统一管理,可能无限制新建线程,相互之间竞争,很可能占用过多系统资源导致死机或OOM。同时,new Thread的线程缺乏更多功能,如定时执行、定期执行、线程中断。
Java 5开始,JDK提供了4中线程池(newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool、newSingleThreadExecutor)来获取线程。这样做的好处是:可以重用已经存在的线程,减少对象创建、消亡的开销,性能佳;而且线程池可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。通过特定的线程池也可以实现定时执行、定期执行、单线程、并发数控制等功能。
创建线程的代码实现
- 扩展java.lang.Thread类
- 自定义一个类继承java.lang.Thread
- 重写Thread的run(),把自定义线程的任务定义在run方法上
- 实例化自定义的Thread对象,并调用start()启动线程
//1.自定义一个类继承Thread类
public class ExThread extends Thread {
//2.重写run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+”:”+i);
}
} public static void main(String[] args) {
//3.创建Thread子类对象
ExThread exThread = new ExThread();
//4.调用start方法启动自定义线程
exThread.start();
}
}
- 实现java.lang.Runnable接口
- 自定义一个类实现Runnable接口
- 实现Runnable接口中的run(),把自定义线程的任务定义在run方法上
- 创建Runnable实现类的对象
- 创建Thread对象,并且把Runnable实现类的对象作为参数传递
- 调用Thread对象的start()启动自定义线程
//1.自定义一个类实现Runnable接口
public class ImThread implements Runnable{
//2.实现run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+”:”+i);
}
} public static void main(String[] args) {
//3.创建Runnable实现类对象
ImThread imThread = new ImThread();
//4.创建Thread对象
Thread thread = new Thread(imThread);
//5.调用start()开启线程
thread.start();
}
}
- newFixedThreadPool
创建一个固定线程数的线程池,可控制线程最大并发数,超出的线程会在队列中等待
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class CreateThreadByFixedPool { /**
* Cover Runnable.run()
*/
private static void run(){
System.out.println(Thread.currentThread().getName()+" is running...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
pool.execute(CreateThreadByFixedPool::run);
}
}
}
- newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.
线程池的容量为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class CreateThreadByCachedPool { public static void main(String[] args) { ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.execute(() -> System.out.println(Thread.currentThread().getName()+" is running..."));
}
}
}
- newScheduledThreadPool
创建一个固定线程数的线程池,支持定时及周期性任务执行。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; public class CreateThreadByScheduledPool { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
//delay 2s excute.
pool.schedule(() -> System.out.println(Thread.currentThread().getName()+" delays 2s "),
2, TimeUnit.SECONDS);
//delay 2s and every 3s excute.
pool.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName()+" delays 2s every 3s execte"),
2, 3, TimeUnit.SECONDS);
}
}
- newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public class CreateThreadBySingleThreadPool {
public static void main(String[] args) {
ExecutorService pool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
pool.execute(() ->{
System.out.println(String.format("The thread %d (%s) is running...",
index,Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
Thread负责线程本身相关的职责和控制,Runnable负责逻辑业务。
- Java是单继承多实现的,实现接口有利于程序拓展
- 实现Runnable接口可为多个线程共享run() 【继承Thread类,重写run()只能被该线程使用】
- 不同线程使用同一个Runnable,不用共享资源
线程中断
- 当对一个线程调用interrupt方法时,线程的中断状态(boolean标志)会被置位。
- 判断当前线程是否中断,可使用Thread.currentThread.isInterrupt()
- 中断并不是强制终止线程,中断线程只是引起当前线程的注意,由它自己决定是否响应中断。【有些(非常重要的)线程会处理完异常后继续执行,并不理会中断;但是更加普遍的情况是:线程简单的将中断作为一个终止请求。】
线程销毁
- 线程结束,自行关闭
- 异常退出
- 通过interrupt()修改isInterrupt()标志进行结束
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
@Override
public void run() {
System.out.println("I will start work.");
while(!isInterrupted()){
System.out.println("working....");
}
System.out.println("I will exit.");
}
};
t.start();
TimeUnit.MICROSECONDS.sleep(100);
System.out.println("System will exit.");
t.interrupt();
}
- 使用volatile修饰的开关flag关闭线程(因为线程的interrupt标识很可能被擦除)【chapter04.FlagThreadExit】
public class FlagThreadExit {
static class MyTask extends Thread{
private volatile boolean closed = false;
@Override
public void run() {
System.out.println("I will start work.");
while(!closed && !isInterrupted()){
System.out.println("working....");
}
System.out.println("I will exit.");
}
public void closed(){
this.closed = true;
this.interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
task.start();
TimeUnit.MICROSECONDS.sleep(100);
System.out.println("System will exit.");
task.closed();
}
}
多线程API
|
方法
|
返回值
|
作用
|
|
|
yield()
|
static void
|
暂停当前正在执行的线程对象,并执行其他线程。
|
只有优先级大于等于该线程优先级的线程(包括该线程)才有机会被执行
释放CPU资源,不会放弃monitor锁
|
|
sleep()
|
static void
|
使当前线程休眠,其它任意线程都有执行的机会
|
释放CPU资源,不会放弃monitor锁
|
|
wait()
|
void
|
使当前线程等待
|
Object的方法
|
|
interrupt()
|
void
|
中断线程
|
可中断方法
|
|
interrupted()
|
static boolean
|
判断当前线程是否中断
|
|
|
isInterrupted()
|
boolean
|
测试线程是否已经中断
|
|
|
join()
|
void
|
在线程A内,join线程B,线程A会进入BLOCKED状态,直到线程B结束生命周期或者线程A的BLOCKED状态被另外的线程中断
|
可中断方法
|
- 可中断方法被打断后会收到中断异常InterruptedException.
- yield()和sleep()的比较
- 都是Thread类的静态方法
- 都会使当前处于运行状态的线程放弃CPU
- yield只会让位给相同或更高优先级的线程,sleep让位给所有的线程
- 当线程执行了sleep方法后,将转到阻塞状态,而执行了yield方法之后,则转到就绪状态;
- sleep方法有可能抛出异常,而yield则没有【sleep是可中断方法,建议使用sleep】
- sleep和wait的比较
- wait和sleep方法都可以使线程进入阻塞状态
- wait和sleep都是可中断方法
- wait使Object的方法,sleep是Thread的方法
- wait必须在同步代码中执行,而sleep不需要
- 在同步代码中,sleep不会释放monitor锁,而wait方法会释放monitor锁
- sleep方法在短暂休眠后主动退出阻塞,而(没有指定时间的)wait方法则需要被其它线程notify或interrupt才会退出阻塞
wait使用
- 必须在同步当法中使用wait和notify方法(wait和notify的前提是必须持有同步方法的monitor的所有权)
- 同步代码的monitor必须与执行wait和notify方法的对象一致
public class WaitDemo {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(() -> {
synchronized (WaitDemo.class){
System.out.println("Enter Thread1...");
System.out.println(Thread.currentThread().getName()+" is waiting...");
try {
WaitDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread1 is going...");
System.out.println("Shut down Thread1.");
}
});
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.execute(() ->{
synchronized (WaitDemo.class) {
System.out.println("Enter Thread2...");
System.out.println(Thread.currentThread().getName()+" is notifying other thread...");
WaitDemo.class.notify();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread2 is going...");
System.out.println("Shut down Thread2.");
}
});
}
}
补充
- 弃用stop()和suspend()的原因
- suspend()的替代方案
- 在实际开发中,调用start()启动线程的方法已不再推荐。应该从运行机制上减少需要并行运行的任务数量。如果有很多任务,要为每个任务创建一个独立线程的编程所付出的代价太大了。可以使用线程池来解决这个问题。
- 线程信息查看工具:JDK自带的Jconsole
并发编程之线程创建到销毁、常用API的更多相关文章
- Java并发编程之线程创建和启动(Thread、Runnable、Callable和Future)
这一系列的文章暂不涉及Java多线程开发中的底层原理以及JMM.JVM部分的解析(将另文总结),主要关注实际编码中Java并发编程的核心知识点和应知应会部分. 说在前面,Java并发编程的实质,是线程 ...
- python并发编程之线程(创建线程,锁(死锁现象,递归锁),GIL锁)
什么是线程 进程:资源分配单位 线程:cpu执行单位(实体),每一个py文件中就是一个进程,一个进程中至少有一个线程 线程的两种创建方式: 一 from threading import Thread ...
- Java并发编程:如何创建线程?
Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...
- 【转】Java并发编程:如何创建线程?
一.Java中关于应用程序和进程相关的概念 在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认是java.exe或者javaw.exe(windows下可以通过 ...
- 2、Java并发编程:如何创建线程
Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...
- Java并发编程:线程的创建
Java并发编程:线程的创建 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} J ...
- Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- 并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- Java并发编程:线程池的使用(转)
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
随机推荐
- java 购物商城小项目训练
java web 模拟购物车练习(项目一) 首页(index.jsp) <div align="center" class="index"> < ...
- 分布式ID系列(5)——Twitter的雪法算法Snowflake适合做分布式ID吗
介绍Snowflake算法 SnowFlake算法是国际大公司Twitter的采用的一种生成分布式自增id的策略,这个算法产生的分布式id是足够我们我们中小公司在日常里面的使用了.我也是比较推荐这一种 ...
- 强烈推荐 GitHub 上值得前端学习的开源实战项目
强烈推荐 GitHub 上值得前端学习的开源实战项目. Vue.js vue-element-admin 是一个后台前端解决方案,它基于和 element-ui 实现 基于 iView 的 Vue 2 ...
- 谈自由,ASP.NET Core才是未来?
首先我要说一下自己对自由的理解: 自由是我可以选择不干什么,但我要保留我可以干什么的可能性. 比如说我现在只有一个码农的角色,但我仍然要保留我可以扮演其他角色的可能, 比如成为一个作者,当我写下文章的 ...
- 使用Mybatis-Generator 自动生成代码
前提:已经有SpringBoot的工程,且在上面实现了MyBatis的应用,只不过全是以手动方式创建mapper.xml映射文件.pojo类等. 在POM中添加MyBatis.generator依赖 ...
- 综述 | SLAM回环检测方法
本文作者任旭倩,公众号:计算机视觉life成员,由于格式原因,公式显示可能出问题,建议阅读原文链接:综述 | SLAM回环检测方法 在视觉SLAM问题中,位姿的估计往往是一个递推的过程,即由上一帧位姿 ...
- Tesseract:简单的Java光学字符识别
1.1 介绍 开发具有一定价值的符号是人类特有的特征.对于人们来说识别这些符号和理解图片上的文字是非常正常的事情.与计算机那样去抓取文字不同,我们完全是基于视觉的本能去阅读它们. 另一方面,计算机的工 ...
- Java线程之线程简介
Java线程之线程简介 一.何谓线程 明为跟踪处理流程,实为跟踪线程 阅读程序时,我们会按处理流程来阅读. 首先执行这条语句 ↓ 然后执行这条语句 ↓ 接着再执行这条语句…… 我们就是按照上面这样的流 ...
- Windows上提高工作效率的神器推荐
Everything 下载地址:http://www.voidtools.com/ 功能:硬盘文件搜索,比起电脑自带的文件搜索,效率提高不是一丁半点.而且Everything还支持正则表达式,小巧而快 ...
- GMTC 2019-前端夜话《聊聊前端工程师的成长和发展》会后简要总结
今天晚上去参加了winter主持的前端夜话:聊聊前端工程师的成长和发展圆桌论坛分享会,真的是收益颇多,这次的这个嘉宾阵容也是很有诚意的,在现在前端领域都是有一定影响力的嘉宾,嘉宾阵容也列一下: 主 ...