并发编程之线程创建到销毁、常用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并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
随机推荐
- 三层架构(MVC)实现简单登陆注册验证(含验证码)
前言在我的上一篇微博里我已经提出了登陆的方法,当时我采取的是纯servlet方式,因为当时刚接触到servlet,正好网上没有这方面的全面讲解,所以我就发飙了.不过在现实生产中我们大多采用的三层架构. ...
- 从Dictionary源码看哈希表
一.基本概念 哈希:哈希是一种查找算法,在关键字和元素的存储地址之间建立一个确定的对应关系,每个关键字对应唯一的存储地址,这些存储地址构成了有限.连续的存储地址. 哈希函数:在关键字和元素的存储地址之 ...
- c# NPOI 导出23万条记录耗时12秒
先上测试代码: string connectionString = "Server=localhost;Initial Catalog=******;User ID=sa;Password= ...
- Python模块之pexpect
一.pexpect模块介绍 Pexpect使Python成为控制其他应用程序的更好工具.可以理解为Linux下的expect的Python封装,通过pexpect我们可以实现对ssh,ftp,pass ...
- Java进程故障排查
故障分析 # 导致系统不可用情况(频率较大): 1)代码中某个位置读取数据量较大,导致系统内存耗尽,进而出现Full GC次数过多,系统缓慢: 2)代码中有比较消耗CPU的操作,导致CPU过高,系统运 ...
- Xshell登陆服务器及Linux的简单命令
在之前的推文中,我已经给出了怎样利用Git登陆服务器”你在用xshell,putty登陆?推荐一个小工具(Git)登陆“其中包括xshell登陆服务器.今天讲讲常见的Linux命令,这个和之前将的利用 ...
- 台式机主机u盘安装centos7报错及注意事项
利用UltraISO制作U盘启动安装台式机CentOS7系统:流程及报错解决 一.制作U盘 1.首先打开UltraISO软件,尽量下载最新版的 2.点击工具栏中的第二个打开镜像文件工具,如图红色方框标 ...
- jQuery Validate 可选项
- Vue入门到TodoList练手
学习资料 慕课网 - vue2.5入门 基础语法 示例代码1 <div id="root"> <h1>hello {{msg}}</h1> &l ...
- 跟我学SpringCloud | 第十六篇:微服务利剑之APM平台(二)Pinpoint
目录 SpringCloud系列教程 | 第十六篇:微服务利剑之APM平台(二)Pinpoint 1. Pinpoint概述 2. Pinpoint主要特性 3. Pinpoint优势 4. Pinp ...