在前面一篇介绍了线程的生命周期[并发编程之多线程概念 ] , 在本篇将正式介绍如何创建, 中断线程, 以及线程是如何销毁的. 最后, 我们会讲解一些常见的线程 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
来源: https://www.cnblogs.com/BlueStarWei/p/11497198.html