Java 之多线程
2020.04.15
zhouxelf
后端
 热度
℃
概述
Java 给多线程编程提供了内置的支持。
线程:一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个线程不能独立存在,它必须是进程的一部分。
进程:一个进程包括由操作系统分配的内存空间,包含 一个或多个 线程。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
一个线程的生命周期
新建状态:使用 new 关键字和 Thread 类或及其子类建立一个线程对象后,该线程对象就处于新建状态,直到执行 start()。
就绪状态:调用 start() 之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,等待 JVM 里线程调度器的调度。
运行状态:就绪状态的线程获取 CPU 资源,就可以执行 run(),之后线程处于运行状态。该状态的线程最为复杂,它可以变为阻塞状态、就绪状态或死亡状态。
阻塞状态:如果线程执行了 sleep()、suspend() 等方法,失去所占资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后重新进入就绪状态。阻塞状态还可以分为三种:
等待阻塞:运行中的线程执行 wait() 方法,进入等待阻塞状态;
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用);
其他阻塞:通过调用线程的 sleep() 或 join() 发出 I/O 请求时,线程就进入阻塞状态。当 sleep() 状态超时,join() 等待线程终止或超时,或 I/O 处理完毕,线程重新进入就绪状态。
死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时,线程就会切换到终止状态。
线程的优先级
每个 Java 线程都有一个优先级,有助于操作系统确定调度顺序。
Java 线程的优先级是一个整数,取值范围是 1-10 (Thread.MIN_PRIORITY - MAX_PRIORITY)。
每个 Java 线程默认分配一个优先级 5(NORM_PRIORITY)。
具有优先级搞的线程应该对程序更重要,应该在优先级低的线程之前分配处理器资源。但是,线程的优先级不能保证线程执行的顺序,也非常依赖于平台。
创建一个线程 Java 提供了三种方法创建线程:
通过实现 Runnable 接口;
通过继承 Thread 类本身;
通过 Callable 和 Future 创建线程。
通过实现 Runnable 接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class RunnableDemo implements Runnable { private Thread t; private final String threadName; RunnableDemo(String name) { threadName = name; System.out.println("Creating " + threadName); } @Override public void run () { System.out.println("Running " + threadName); try { for (int i = 4 ; i > 0 ; i--) { System.out.println("Thread: " + threadName + ", " + i); Thread.sleep(50 ); } } catch (InterruptedException e) { System.out.println("Thread " + threadName + "interrupted." ); } System.out.println("Thread " + threadName + " exiting." ); } public void start () { System.out.println("Starting " + threadName); if (t == null ) { t = new Thread(this , threadName); t.start(); } } } public class ThreadTest_1 { public static void main (String[] args) { RunnableDemo R1 = new RunnableDemo("Thread-1" ); R1.start(); RunnableDemo R2 = new RunnableDemo("Thread-2" ); R2.start(); } }
通过继承 Thread 类本身 本质上也是实现了 Runnable 接口的一个实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class ThreadDemo extends Thread { private Thread t; private final String threadName; ThreadDemo(String name) { threadName = name; System.out.println("Creating " + threadName); } public void run () { System.out.println("Running " + threadName); try { for (int i = 4 ; i > 0 ; i--) { System.out.println("Thread: " + threadName + ", " + i); Thread.sleep(50 ); } } catch (InterruptedException e) { System.out.println("Thread " + threadName + "interrupted." ); } System.out.println("Thread " + threadName + " exiting." ); } public void start () { System.out.println("Starting " + threadName); if (t == null ) { t = new Thread(this , threadName); t.start(); } } } public class ThreadTest_2 { public static void main (String[] args) { ThreadDemo T1 = new ThreadDemo("Thread-1" ); T1.start(); ThreadDemo T2 = new ThreadDemo("Thread-2" ); T2.start(); } }
通过 Callable 和 Future 创建线程
创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值;
创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值;
使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程;
调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class CallableThreadTest implements Callable <Integer > { @Override public Integer call () { int i = 0 ; for (; i < 10 ; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); } return i; } public static void main (String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for (int i = 0 ; i < 10 ; i++) { System.out.println(Thread.currentThread().getName() + " 的循环变量 i 的值: " + i); if (i == 5 ) { new Thread(ft, "有返回值的线程" ).start(); } } try { System.out.println("子线程的返回值: " + ft.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } }
创建线程的三种方式对比
采用实现 Runnable、Callable 接口的方式创建多线程时,线程只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
线程的几个主要概念
线程同步
线程间通信
线程死锁
线程控制:挂起、停止和恢复
多线程的使用 有效利用多线程的关键是理解程序是 并发执行 而不是 串行执行 的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了 。
请记住,上下文的切换开销也很重要 ,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!