一、线程、程序、进程的定义及区别
程序:一组计算机能识别和执行的指令、它是一些保存在磁盘上的指令的有序集合。 进程:程序的一次执行过程, 是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。 线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。 进程与线程区别:- 线程与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程
- 在各个线程之间做切换工作时,负担要比进程小得多
二、实现多线程的方法
实现线程的方法有很多种
-
①继承
Thread
类; -
②实现
Runnable
接口; -
③实现
Callable
接口; -
④使用
ExecutorService
线程池; -
⑤使用
CompletableFuture
类; -
⑥基于
ThreadGroup
线程组; -
⑦使用
FutureTask
类; -
⑧使用匿名内部类或
Lambda
表达式; -
⑨使用
Timer
定时器类; -
⑩使用
ForkJoin
线程池或Stream
并行流。
在这里简单列举四种:
- 继承Thread类:
-
这是最普通的方式,通过 继承
Thread
类,重写run
方法,来实现多线程。 public class ExtendsThread extends Thread { @Override public void run() { System.out.println("1......"); } public static void main(String[] args) { new ExtendsThread().start(); } }
-
实现Runnable接口
public class ImplementsRunnable implements Runnable { @Override public void run() { System.out.println("2......"); } public static void main(String[] args) { ImplementsRunnable runnable = new ImplementsRunnable(); new Thread(runnable).start(); } }
这也是一种常见的方式,通过实现
Runnable
接口并重写run
方法来实现多线程。 -
实现Callable接口
public class ImplementsCallable implements Callable<String> { @Override public String call() throws Exception { System.out.println("3......"); return "zhuZi"; } public static void main(String[] args) throws Exception { ImplementsCallable callable = new ImplementsCallable(); FutureTask<String> futureTask = new FutureTask<>(callable); new Thread(futureTask).start(); System.out.println(futureTask.get()); } }
和上一种方式类似,只不过这种方式可以拿到线程执行完的返回值。
-
使用ExecutorService线程池
public class UseExecutorService { public static void main(String[] args) { ExecutorService poolA = Executors.newFixedThreadPool(2); poolA.execute(()->{ System.out.println("4A......"); }); poolA.shutdown(); // 又或者自定义线程池 ThreadPoolExecutor poolB = new ThreadPoolExecutor(2, 3, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); poolB.submit(()->{ System.out.println("4B......"); }); poolB.shutdown(); } }
这种属于进阶方式,可以通过
Executors
创建线程池,也可以自定义线程池。
三、线程生命周期和状态
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:
- NEW: 初始状态,线程被创建出来但没有被调用
start()
。 - RUNNABLE: 运行状态,线程被调用了
start()
等待运行的状态。 - BLOCKED:阻塞状态,需要等待锁释放。
- WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
- TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
- TERMINATED:终止状态,表示该线程已经运行完毕。
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。
Java 线程状态变迁图
由上图可以看出:线程创建之后它将处于 NEW(新建) 状态,调用 start()
方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。
- 当线程执行
wait()
方法之后,线程进入 WAITING(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。 - TIMED_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过
sleep(long millis)
方法或wait(long millis)
方法可以将线程置于 TIMED_WAITING 状态。当超时时间结束后,线程将会返回到 RUNNABLE 状态。 - 当线程进入
synchronized
方法/块或者调用wait
后(被notify
)重新进入synchronized
方法/块,但是锁被其它线程占有,这个时候线程就会进入 BLOCKED(阻塞) 状态。 - 线程在执行完了
run()
方法之后将会进入到 TERMINATED(终止) 状态。