2019独角兽企业重金招聘Python工程师标准>>>

在计算机世界,当人们谈到并发时,它的意思是一系列的任务在计算机中同时执行。如果计算机有多个处理器或者多核处理器,那么这个同时性是真实发生的;如果计算机只有一个核心处理器那么就只是表面现象。

现代所有的操作系统都允许并发地执行任务。你可以在听音乐和浏览网页新闻的同时阅读邮件,我们说这种并发是进程级别的并发。而且在同一进程内,也会同时有多种任务,这些在同一进程内运行的并发任务称之为线程。

在这里我们要讨论的是线程级并发。Java提供了Thread类,使我们能够在一个Java进程中运行多个线程,每个线程执行不同的任务,以此实现并发。

1、创建线程                                                                                                 

在Java中,我们有2个方式创建线程:

  1. 通过直接继承thread类,然后覆盖run()方法。
  2. 构建一个实现Runnable接口的类, 然后创建一个thread类对象并传递Runnable对象作为构造参数

可以看到,这两种创建线程的方式都需要新建一个Thread对象,可以说一个Thread对象代表一个线程实例。

由于Java是单继承的,如果我们使用第一种方式创建线程,就强制只能继承Thread,灵活性较低,对于第二种创建线程方式就没有这个问题,所以我们一般选择第二种方式创建线程。下面我们就使用第二种创建方式举一个简单的例子,首先,实现Runnable接口,输出1到10:

public class MyRunnable implements Runnable {@Overridepublic void run() {for(int i =  0; i < 10; i++) {System.out.println(i);}}
}

然后,建立main方法,新建线程并启动:  

public class Main {public static void main(String[] args) {for(int i = 0; i < 10; i++) {new Thread(new MyRunnable()).start();}}
}

  可以看到使用这种方法创建线程,我们一共创建了两个类,一个类实现了Runnable接口,代表一个运行任务;一个类是运行类,在这个运行类的main方法中,我们利用刚刚定义的Runnable实例新建Thread并运行。利用这种方法创建线程,相较于第一种,代码逻辑十分清楚:一个类定义任务,一个类运行任务,所以推荐这种方式创建线程。

2、运行线程                                                                                                

运行一个线程的方法十分简单,我们只需调用Thread实例的start()方法即可,当我们调用start()方法之后,Java虚拟机会为我们创建一个线程,然后在该线程中运行run()方法中定义的任务,真正实现多线程。

在这里,必须强调的是应该调用start()方法,而不是run()方法,直接调用run()方法,虚拟机不会新建线程运行任务,只会在当前线程执行任务,无法实现多线程并发,所以应该调用start()方法。

3、停止线程                                                                                                

相较于创建与运行线程的简单,在Java中停止一个线程其实并不容易。Thread类虽然提供了stop()方法用于停止线程,但是该方法具有固有的不安全性。用 Thread.stop 来终止线程将释放它已经锁定的所有监视器。如果以前受这些监视器保护的任何对象处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为,该方法已经被标注为过时的了,我们不应该使用该方法。

3.1、"已请求取消"标志

既然如此,那么我们应该如何停止一个线程呢?我们可以使用"已请求取消"标志的方法停止线程。我们先在任务中定义该标志,然后任务会定期的查看该标志,如果设置了这个标志,那么任务将提前结束。以下程序使用该项技术来持续枚举素数,直到它被取消,注意,为保证这个过程能可靠地工作,标志必须设置为volatile类型:

public class PrimeGenerator implements Runnable {private final List<BigInteger> primes = new ArrayList<BigInteger>();private volatile boolean cancelled;public void run() {BigInteger p = BigInteger.ONE;while (!cancelled) {p = p.nextProbablePrime();synchronized (this) {primes.add(p);}}}public void cancel() {cancelled = true;}public synchronized List<BigInteger> get() {return new ArrayList<BigInteger>(primes);}
}

  在这里,PrimeGenerator类提供了cancel()方法,当这个方法被调用后,cancelled标志位就会被置为true,然后run()方法中的while循环就会结束,整个线程也就结束。

3.2、使用中断停止线程

使用"已请求取消"标志取消任务有一个问题,如果线程执行的是阻塞任务,那么线程将永远不会去检测取消标志,因此永远不会结束。

当出现这种情况时,我们该如何结束线程呢?有部分阻塞库方法是支持中断的,线程中断是一种协作机制,线程可以通过这种机制来通知另一个线程,告诉它在合适的或者可能的情况下停止当前工作,并转而执行其他工作。

我们可以修改上面的例子,不使用ArrayList存储素数结果,而使用BlockingQueue来存储,BlockingQueue的put()方法是可阻塞的,如果依然使用"已请求取消"标志的结束策略,同时put()方法被阻塞住后,那么该方法将永远不会停止,对于这种情况,我们可以使用检测中断标志位的方法来判断结束线程:

public class PrimeProducer extends Thread {private final BlockingQueue<BigInteger> queue;PrimeProducer(BlockingQueue<BigInteger> queue) {this.queue = queue;}public void run() {try {BigInteger p = BigInteger.ONE;while (!Thread.currentThread().isInterrupted())queue.put(p = p.nextProbablePrime());} catch (InterruptedException consumed) {/* Allow thread to exit */}}public void cancel() {interrupt();}
}

  这一次,cancle()方法不再设置结束标志位,而是调用interrupt()进行线程中断。当cancel()方法被调用之后,当前线程的中断标志位将被置为true,BlockingQueue的put()方法能够响应中断,并从阻塞状态返回,返回后,while语句检测到中断位被标志,然后结束while循环,整个线程结束。

3.3、不能响应中断的阻塞方法

如果阻塞方法能够响应中断,那我们就可以使用以上的方法结束线程,但是Java类库中还有一些阻塞方法是不能够响应中断的,这些方法包括:

  • java.io包中的同步Socket I/O
  • java.io包中的同步I/O
  • Selector的异步I/O
  • 获取某个锁

不过还好,对于I/O流,我们可以使用关闭底层I/O流的方式结束线程,以下代码给出了如何封装非标准的取消操作的例子,ReaderThread管理一个套接字连接,它采用同步方式从套接字中读取数据,为了结束某个用户的连接或者关闭服务器,ReaderThread改写了interrupt方法,使其既能处理标准中断,也能关闭底层套接字。

public class ReaderThread extends Thread {private static final int BUFSZ = 512;private final Socket socket;private final InputStream in;public ReaderThread(Socket socket) throws IOException {this.socket = socket;this.in = socket.getInputStream();}public void interrupt() {try {socket.close();} catch (IOException ignored) {} finally {super.interrupt();}}public void run() {try {byte[] buf = new byte[BUFSZ];while (true) {int count = in.read(buf);if (count < 0)break;else if (count > 0)processBuffer(buf, count);}} catch (IOException e) { /* Allow thread to exit */}}public void processBuffer(byte[] buf, int count) {}
}

  对于这个具体的Thread类来说,我们调用interrupt()方法,线程首先会把socket关闭,然后再finally()中设置中断标志位。关闭socket之后,run()方法中的socket读取将立即抛异,catch子句将捕获该异常并顺利停止该线程。

3.4、总结

最后,让我对上面的内容总结一下:

要结束一个线程,最理想方式是让其自动结束,如果你想提前结束线程的运行,那么需要区分三种情况。

1、如果允许代码中不存在阻塞方法,你可以设置一个"结束"标志位,然后不停的检测它,当它为true时,主动结束线程;

2、如果代码中存在阻塞方法,且该方法能够响应中断,那么你可以调用Thread.interput()结束线程;

3、如果代码中存在阻塞方法,且该方法不能够响应中断,那么就需要通过关闭底层资源,让代码抛出异常的方式结束线程。

转载于:https://my.oschina.net/u/197668/blog/361242

Java并发(基础知识)—— 创建、运行以及停止一个线程相关推荐

  1. java 一个线程运行_Java并发(基础知识)—— 创建、运行以及停止一个线程

    在计算机世界,当人们谈到并发时,它的意思是一系列的任务在计算机中同时执行.如果计算机有多个处理器或者多核处理器,那么这个同时性是真实发生的:如果计算机只有一个核心处理器那么就只是表面现象. 现代所有的 ...

  2. Java并发基础知识(五)

    线程池 为什么要用线程池? Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池能够带来3个好处. 第一:降低资源消耗.通过 ...

  3. Java并发基础知识,我用思维导图整理好了

    文章目录 1.基本概念 2.线程创建和运行 3.常用方法 3.1.线程等待与通知 3.2.线程休眠 3.3.让出优先权 3.4.线程中断 4.线程状态 5.线程上下文切换 6.线程死锁 7.线程分类 ...

  4. java核心技术 基础知识<集合并发part1>

    文章目录 java核心技术 基础知识<集合&并发part1> 9 泛型程序设计 9.5 算法 9.6 遗留的集合 14 并发 14.2 中断线程 14.3 线程状态 14.4 线程 ...

  5. Java面试基础知识III

    Java面试基础知识: 1.C++或Java中的异常处理机制的简单原理和应用. 当JAVA 程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常.违反语义规则包括2种情况.一种 ...

  6. Java SE 基础知识

    Java SE 基础知识 1 2 @(Notes)[J2SE, Notes] VICTORY LOVES PREPARATION. 特别说明: 该文档在马克飞象查阅最佳: 本部分知识还在迭代中,欢迎补 ...

  7. Java并发基础(六) - 线程池

    Java并发基础(六) - 线程池 1. 概述 这里讲一下Java并发编程的线程池的原理及其实现 2. 线程池的基本用法 2.1 线程池的处理流程图 该图来自<Java并发编程的艺术>: ...

  8. 黑马程序员 JAVA相关基础知识

    1.面向对象的语言特征 封装.继承.多态 2.Java I/O中字符流和字节流的区别 字节流是传递0~255的整数而Java中有一些(如unicode代码)不能使用字符流,是特殊的字节流, 3.Has ...

  9. Java进阶基础知识

    Java进阶基础知识 1.Java 基础 Java类设计的原则就是内聚性,一致性和封装性是Java设计的基本原则 1.1 Java基础理论 Java基础理论知识 1.2继承的优缺点 1.2.1优点 : ...

  10. 你觉得什么才是 Java 的基础知识?

    近日里,很多人邀请我回答各种j2ee开发的初级问题,我无一都强调java初学者要先扎实自己的基础知识,那什么才是java的基础知识?又怎么样才算掌握了java的基础知识呢?这个问题还真值得仔细思考. ...

最新文章

  1. 我的linux Mint之路(三)
  2. 微软一站式示例代码库 2012 年2月示例代码更新。8个全新示例为您的开发保驾护航...
  3. centos 7 安装 npm 工具
  4. Linux文件系统与日志分析(inode、inode节点耗尽故障处理、文件备份和恢复、日志文件管理)
  5. PyTorch 1.6 发布:原生支持自动混合精度训练并进入稳定阶段
  6. 【Storm入门指南】第六章 真实示例
  7. 制作属于自己的个人博客-超详细教程
  8. 大数运算(加、减、乘、除)
  9. 雷凌3070无线网卡linux驱动,3070无线网卡驱动,小编教你ralink3070无线网卡驱动
  10. 使用Flask部署机器学习模型
  11. 基于Springboot+MyBatisPlus+Vue前后端分离大学生毕业论文答辩系统
  12. 电源技术中的onsemi ESD5B5.0ST1G,ESD9B3.3ST5G,ESD9B5.0ST5G,SZESD9B5.0ST5G,ESD静电保护管 TVS管 电容值低,反应速度快的解决方案
  13. 读书笔记 - 富爸爸穷爸爸
  14. [羊城杯 2020]Easyphp2
  15. 还在做手搓党和模拟器党?手机投屏电脑玩吃鸡你值得拥有
  16. php批量改名工具,批量更名大师下载_批量改名大师(Win-Tool之文件批量更名工具) 1.8.7 演示版_极速下载站_软件下载...
  17. 如何设置段落格式html,dreamweaver设置段落格式的方法
  18. 二、肺癌检测-LUNA数据集下载和介绍
  19. 比尔总动员日常任务攻略一
  20. 【赠书福利】掘金爆火小册同名《Spring Boot源码解读与原理剖析》正式出书了!...

热门文章

  1. Linux 内核C -- 第01课,C 语言语法扩展
  2. Linux Workqueue
  3. 汽车诊断协议,(K线/CAN总线、kwp2000、ISO14230、ISO1575...)转载
  4. 营销管理手册_从店长手册入手,推动营销管理大升级
  5. 【思维、费马小定理】CQXYM Count Permutations
  6. 思科网院Packet Tracer实验(七)IP编址
  7. php项目部署到linux服务器,thinkphp项目部署到Linux服务器上报错“模板不存在”如何解决...
  8. android 转场动画 共享元素,Android-Animation-Set
  9. Java vbnullstring_VB中Null、Empty、Nothing及vbNullString的区别
  10. go 调用 另一个go 的方法_Go 经典入门系列 17:方法