在前面一篇介绍了线程的生命周期【并发编程之多线程概念】,在本篇将正式介绍如何创建、中断线程,以及线程是如何销毁的。最后,我们会讲解一些常见的线程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负责逻辑业务。

  在实现自定义线程时,推荐使用Runnable接口,因为其具有以下几个优点:
  • Java是单继承多实现的,实现接口有利于程序拓展
  • 实现Runnable接口可为多个线程共享run() 【继承Thread类,重写run()只能被该线程使用】
  • 不同线程使用同一个Runnable,不用共享资源

线程中断

    interrupt()方法可以用来请求终止线程。
  • 当对一个线程调用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()的原因
                stop()用来终止一个线程,但是不安全的; stop()会终止当前线程的所有未结束的方法,包括run()。当前线程被终止,立即释放被它锁住的锁。这会导致对象处于不一致的状态。【在转账过程中,可能钱刚转出,还未转入另一个账户,线程就被中断。导致对象被破坏,数据出错,其他线程得到的将会是错误的对象数据】
                suspend()用来阻塞一个线程,直至另一个线程调用resume()。suspend()会经常导致死锁。 调用suspend()的时候,目标线程会被挂起,但是仍然会持有之前获得的锁定。此时,其他线程都不能访问被锁定的资源。如果调用suspend()的线程试图获取同一个锁,那么线程死锁(被挂起的线程等着恢复,而将其挂起的线程等待锁资源)
  • suspend()的替代方案
                应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

  • 在实际开发中,调用start()启动线程的方法已不再推荐。应该从运行机制上减少需要并行运行的任务数量。如果有很多任务,要为每个任务创建一个独立线程的编程所付出的代价太大了。可以使用线程池来解决这个问题。
  • 线程信息查看工具:JDK自带的Jconsole
 

并发编程之线程创建到销毁、常用API的更多相关文章

  1. Java并发编程之线程创建和启动(Thread、Runnable、Callable和Future)

    这一系列的文章暂不涉及Java多线程开发中的底层原理以及JMM.JVM部分的解析(将另文总结),主要关注实际编码中Java并发编程的核心知识点和应知应会部分. 说在前面,Java并发编程的实质,是线程 ...

  2. python并发编程之线程(创建线程,锁(死锁现象,递归锁),GIL锁)

    什么是线程 进程:资源分配单位 线程:cpu执行单位(实体),每一个py文件中就是一个进程,一个进程中至少有一个线程 线程的两种创建方式: 一 from threading import Thread ...

  3. Java并发编程:如何创建线程?

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  4. 【转】Java并发编程:如何创建线程?

    一.Java中关于应用程序和进程相关的概念 在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认是java.exe或者javaw.exe(windows下可以通过 ...

  5. 2、Java并发编程:如何创建线程

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  6. Java并发编程:线程的创建

    Java并发编程:线程的创建 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} J ...

  7. Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  8. 并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略

    Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...

  9. Java并发编程:线程池的使用(转)

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

随机推荐

  1. Cocos Creator经典游戏制作之:信使(The Messenger)

    版权声明: 本文原创发布于博客园"优梦创客"的博客空间(网址:http://www.cnblogs.com/raymondking123/)以及微信公众号"优梦创客&qu ...

  2. CSV Data Set Config 详细使用说明

    JMeter 5.1.1 CSV Data Set Config 场景一:线程组中设置:单线程执行1次 如上图所示:变量名称为空时JMeter默认把new 1.txt的文件首行作为变量名 再如:此时A ...

  3. React 多副本问题

    Element ref was specified as a string (MySider) but no owner was set. This could happen for one of t ...

  4. 微信小程序页面跳转url如何传对象参数

    两步走 首先第一步:wx.navigateTo({ url:"XXX"+"&params="+ JSON.stringify(obj); }) 第二步获 ...

  5. 对微软的敌视何时休? 从一篇语言评论文章对C#的评价说起

    看到一篇公众号文章<2020年什么编程语言最受欢迎,待遇最高?>,其中对C#的描述如下: 点击阅读原文,看到这是一篇翻译文章:https://codinginfinite.com/top- ...

  6. 学完JavaScript基础有感

    紧接上一篇回来了,这几天一直学js,会不自觉的和其他的编程语言联系在一起,在没有学jQuery之前,结合我所学的c,java,数据结构,数据库以及部分html感觉到JavaScript里面又很多相似的 ...

  7. Spring-boot:多模块打包

    <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot ...

  8. Example With JdbcDaoSupport

    By extended the JdbcDaoSupport, set the datasource and JdbcTemplate in your class is no longer requi ...

  9. Kali Linux-装机后通用配置

    目录 前言 一. 网络优化 更换host 更换dns 添加源 二. 更新系统 三 .安装N卡驱动 四.修复 add-apt-repository 五.安装常用软件 安装apt自带的包 安装第三方的de ...

  10. Spring Boot 默认指标从哪来?

    了解有关 Spring Boot 默认指标及其来源的更多信息. 您是否注意到 Spring Boot 和 Micrometer 为您的应用生成的所有默认指标? 如果没有 - 您可以将 actuator ...