线程和进程
Process 进程是 System 级单位,有独立运行空间,切换会有较大开销。
Thread 线程是处理器 CPU 调度单位,同一类共享代码和数据空间,比 Process 轻量,切换的开销小。
一个 Process 是一个 Java 程序的运行,包含至少一个或 N 个 Thread。
创建一个线程
创建一个 Thread,可以直接使用 Thread Class。或者使用以下三种方法自定义:
- 继承 Thread 类:简单,无法继承。重写 run 方法。
- 实现 Runnable 接口:复杂,可以继承。重写 run 方法。
- 实现 Callable 接口:同 Runnable。重写 call 方法。
其中,继承 Thread 类和实现 Runnable 接口,无法获得执行结果,无法处理执行异常。
但是位于 java.util.concurrnet
中的 Callable
解决了该问题,call 方法是 run 方法增强版,可以在实现时,通过传入泛型的方式,定义返回值。
例子:继承 Thread 类
class Scratch extends Thread{
@Override
public void run() {
System.out.println("Thread is Created");
}
public static void main(String[] args) {
Scratch thread = new Scratch();
thread.start();
System.out.println(Thread.currentThread().getName());
}
}
例子:实现 Runnable 接口
class MyTask implements Runnable {
public void run() {
System.out.println("Task is running");
}
}
public class Main {
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task);
thread.start();
}
}
MyTask
类实现了 Runnable
接口,只定义了可执行的任务,但本身不是线程。Thread
类提供了机制,将任务与线程相互关联。
例子和解读:实现 Callable 接口
接口的定义是:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
补充知识:
@FunctionalInterface
是函数式接口,可以用 Lambda 表达实现:ClassWithFunctionalInterface func = () => System.out.println("ok");
Callable 是 Runnable 的补丁,V
定义了返回值,Exception
定义了异常。
因为最终我们使用 Thread 实现多线程,而它只接受 Runnable,因此需要一个机制,连接 Callable 和 Thread。
解决方案是 FutureTask
类。
该类提供两个构造方法:
FutureTask(Callable callable)
,可以和 Callable 关联。FutureTask(Runnable runnable, V result)
,也支持 Runnable。
该类是 RunnableFuture
接口的实现,源码是:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
Runnable
负责 Thread,Future
负责得到来自 Callable
的返回值。
总结一下逻辑关系是:
Thread -Runnable
+ Callable - Future
-> RunnableFuture
-> FutureTask
用代码逻辑表示是:
// Callable
Scratch scratch = new Scratch();
// To FutureTask
FutureTask<String> future = new FutureTask<>(scratch);
// To Thread
Thread thread = new Thread(future);
完整的 FutureTask
使用例子:
class Scratch implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Scratch");
return "OK";
}
public static void main(String[] args) {
// Callable
Scratch scratch = new Scratch();
// To FutureTask
FutureTask<String> future = new FutureTask<>(scratch);
// To Thread
Thread thread = new Thread(future);
thread.start();
try {
// Get result from FutureTask
String result = future.get(5, TimeUnit.SECONDS);
System.out.println(result);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException | ExecutionException | TimeoutException e) {
e.printStackTrace();
}
}
}
线程的不同状态
通过 new
,我们创建了一个 Thread 对象。在从生到死的完整生命周期中,它包括六个状态:new,runnable,blocked,waiting,time_waiting,terminated。
具体如下:
- New 新建:初识状态,不能运行。JVM 为它分配了内存,没有线程生命特征,和其它 Java 对象一样。
- Runnable 可运行:New 状态,调用
start()
进入。它细分成两个状态:- Ready 就绪:它没运行,只是做好准备,等待 JVM 调度。
- Running 运行:真正运行。JVM 或 CPU 开始调度。
- Blocked 阻塞:因某些原因,失去 CPU 执行权,即 CPU 不执行它。一般会因两种情况进入阻塞:
- [[同步锁]] 被其它线程截胡。
- 线程运行时,发出 IO 请求。
- Waiting 等待:Running 中的线程,调用了
wait()
或join()
方法,进入该状态。等待中的线程,必须等其他线程执行特定操作后,才有机会再次争夺 CPU 使用权,然后转为 Running。例如:wait()
:等待其他线程调用notify()
或notifyAll()
唤醒join()
:等待其他加入的线程终止。
- Timed_waiting 定时等待:Running 中的线程,调用了
sleep(long millis)
、wait(long timeout)
、join(long millis)
等方法进入。类似 Waiting 状态。 - Terminated 终止:
run()
、call()
执行完或者未捕获的 Exception、错误 Error,线程进入终止状态,生命周期结束。
切换不同状态
New -> Runnable
调用 start()
方法,启动线程,然后会自动调用 run()
。
单纯调用 run()
,无法启动线程,参考下面的例子:
public class StartVsRunExample {
public static void main(String[] args) {
// 创建一个实现了 Runnable 接口的匿名类
Runnable task = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i ++) {
System.out.println(Thread.currentThread().getName() + ": Count " + i);
try {
Thread.sleep(500); // 睡眠500毫秒
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread thread1 = new Thread(task, "Thread-1");
thread1.start();
Thread thread2 = new Thread(task, "Thread-2");
thread2.run();
// 主线程继续执行
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": Main Count " + i);
try { Thread.sleep(500); }
catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
输出:
main: Count 0
Thread-1: Count 0
Thread-1: Count 1
main: Count 1
main: Count 2
Thread-1: Count 2
main: Count 3
Thread-1: Count 3
Thread-1: Count 4
main: Count 4
main: Main Count 0
main: Main Count 1
main: Main Count 2
main: Main Count 3
main: Main Count 4
其中,main: Count 0
在主线程中运行,不在线程 thread2
中。
Runnable -> Block
例子:
两个线程,抢通过定义 Object 为 lock
,:
class BlockThread extends Thread {
private String name;
private Object lock;
public BlockThread(String name, Object lock) {
this.name = name;
this.lock = lock;
}
@Override
public void run() {
System.out.println("Thread " + name + " State is "+Thread.currentThread().getState());
synchronized (lock) {
System.out.println("Thread " + name + " hold the lock.");
try {
System.out.println("Thread " + name + " State is "+Thread.currentThread().getState());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + name + " released the lock.");
}
}
}
public class Scratch {
public static void main(String[] args) throws Exception{
Object lock = new Object();
BlockThread t1 = new BlockThread("Thread1", lock);
BlockThread t2 = new BlockThread("Thread2", lock);
t1.start();
t2.start();
Thread.sleep(100);
System.out.println("T1 state is "+t1.getState());
System.out.println("T2 state is "+t2.getState());
}
}
输出:
Thread Thread2 State is RUNNABLE
Thread Thread1 State is RUNNABLE
Thread Thread2 hold the lock.
Thread Thread2 State is RUNNABLE
T1 state is BLOCKED
T2 state is TIMED_WAITING
Thread Thread2 released the lock.
Thread Thread1 hold the lock.
Thread Thread1 State is RUNNABLE
Thread Thread1 released the lock.
Runnable -> Waiting
join()
:让其它线程进入自身,使用 wait()
机制实现。
例如,当 Thread A 执行 threadB.join() 时,线程 A 会进入 WAITING 状态,等待 ThreadB 结束后,再继续。
例子:
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("Thread started");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread finished");
});
thread.start();
System.out.println("Main thread waiting for thread to finish");
thread.join(); // thread join main
System.out.println("Main thread continues");
}
}
wait()
:Object 类方法,每个对象都有,必须在 synchronized block 中使用 ([[同步锁]])。使进程进入等待,直到其他线程,调用同一对象 notify()
或 notifyAll()
。
使用方法:
synchronized (object) {
while (条件不满足) {
object.wait();
}
// 条件满足,继续执行
}
它区别 sleep()
:
sleep()
不释放锁,wait()
释放锁。sleep()
暂停执行,wait()
用于进程通信
例子:
class Scratch {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
Thread t1 = new Thread(() -> {
try {
resource.use();
} catch (Exception e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
resource.prepare();
} catch (Exception e) {
e.printStackTrace();
}
});
// not ready, waiting...
t1.start();
// ready, notify!
t2.start();
}
}
class SharedResource {
private boolean isReady = false;
public synchronized void prepare() {
isReady = true;
notify();
}
public synchronized void use() throws InterruptedException{
while (!isReady) {
wait();
}
System.out.println("I am ready");
isReady = false;
}
}
Waiting -> Runnable
notify()
:通知 wait()
。
例子见上一子章节。