并发编程之线程创建到销毁、常用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并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
随机推荐
- C#将图片转换成字符画
先看一下效果图 在Main方法中调用(首先要添加程序集System.Drawing,然后引入命名空间System.Drawing) ConvertToChar(new Bitmap(@"D: ...
- 在centos6系列vps装Tomcat8.0
In the following tutorial you will learn how to install and set-up Apache Tomcat 8 on your CentOS 6 ...
- MVC + EFCore 完整教程19-- 最简方法读取json配置:自定义configuration读取配置文件
问题引出 ASP.NET Core 默认将 Web.config移除了,将配置文件统一放在了 xxx.json 格式的文件中. 有Web.config时,我们需要读到配置文件时,一般是这样的: var ...
- spring-boot-plus项目目录结构(六)
spring-boot-plus项目目录结构 目录结构 bin:启动/重启命令脚本目录 logs:部署后记录日志目录 assembly:maven打包配置文件目录 java:源代码目录 resourc ...
- iOS学习——iOS 宏(define)与常量(const)的正确使用
概述 在iOS开发中,经常用到宏定义,或用const修饰一些数据类型,经常有开发者不知怎么正确使用,导致项目中乱用宏与const修饰.你能区分下面的吗?知道什么时候用吗? #define HSCode ...
- Code signing is required for product type 'Unit Test Bundle' in SDK 'iOS 11.0.1'
Code signing is required for product type 'Unit Test Bundle' in SDK 'iOS 11.0.1' 进入 projects and lis ...
- deepin 15.11 安装 pyenv
GitHub:官方环境:https://github.com/pyenv/pyenv/wiki/Common-build-problems GitHub:官方文档:https://github.com ...
- js 前端实现打印功能
// 此处是一个打印的方法 可以在点击事件的时候调用 dayin = () =>{ // 获取当前页面要打印的内容 // 这里的className(‘print’)是我给要打印的区域起的 ...
- unity_实用小技巧(相机跟随两个主角移动)
在两人对战的游戏中,有时候我们希望能看清楚两玩家的状态,这时我们需要让相机跟随玩家,可是我们不能让相机只跟随一个玩家移动,这时我们可以取两玩家的中点作为相机的位置.方法如下: public Trans ...
- Centos安装和配置Mysql5.7
[root@localhost ~]# wget https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm -bash ...