并发编程之线程创建到销毁、常用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并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
随机推荐
- vi编辑器基础指令
vi -- 终端中的编辑器 visual interface ssh-- secure shell vim vi improved-----------------------------打开和新建文 ...
- 在canvas中使用其他HTML元素
做一个功能如下图,随机生成100个大小.颜色随机的小球.点击开始运动的时候,小球开始运动,然后点击停止运动的时候,小球停止运动. 点击旁边的白色或者黑色,则背景颜色变为相应的颜色. HTML部分: & ...
- ABAP 查看采购订单行项目已开票金额和已清金额
FUNCTION zmm_fm_po_invence. *"----------------------------------------------------------------- ...
- 从原理层面掌握@ModelAttribute的使用(使用篇)【一起学Spring MVC】
每篇一句 每个人都应该想清楚这个问题:你是祖师爷赏饭吃的,还是靠老天爷赏饭吃的 前言 上篇文章 描绘了@ModelAttribute的核心原理,这篇聚焦在场景使用上,演示@ModelAttribute ...
- Linux之vim详解
第一次使用vim,啥都不懂,输入也不能输入,退出也不会退出,特别的尴尬....后来慢慢的接触学习,发现vim真的挺好用的,不过上手有点慢,多用就对了,用多了我相信你也会喜欢这个文本编辑工具的 一.vi ...
- EXP查询合集提权后渗透必备
0x00 整理的一些后渗透提权需要用到的一些漏洞,后渗透提权的时候可以看一下目标机那些补丁没打,再进行下一步渗透提权. 0x01 CVE-2019-0803 [An elevation of priv ...
- JS实现循环删除数组中元素的方法介绍
这篇文章主要给大家介绍了关于Javascript循环删除数组中元素的几种方法,文中给出了详细的示例代码供大家参考学习,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧. 本文主要跟大家分享了 ...
- Git 实用技巧:git stash
我们经常会遇到这样的情况: 正在dev分支开发新功能,做到一半时有人过来反馈一个bug,让马上解决,但是新功能做到了一半你又不想提交,这时就可以使用git stash命令先把当前进度保存起来.然后切换 ...
- react native 8081 端口号被占
McAfee Agent 占用8081 端口号 公司电脑被标装,McAfee Agent关不掉 sudo lsof -n -i4TCP:8081 sudo launchctl list | grep ...
- Glide3升级到Glide4碰到的问题汇总以及部分代码修改
版权声明:本文为xing_star原创文章,转载请注明出处! 本文同步自http://javaexception.com/archives/188 Glide.3x的版本是3.7.0,Glide4.x ...