并发编程之线程创建到销毁、常用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
创建一个固定线程数的线程池,可控制线程最大并发数,超出的线程会在队列中等待。【线程数量可以参考CPU数量,获取CPU数量:Runtime.getRuntime().availableProcessors()】
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标识很可能被擦除)
public class FlagThreadExit { static class MyTask extends Thread{ private volatile boolean closed = false; @Override public void run() { System.out.println("I will start work.");
//isInterupted()是Thread类中的方法 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