什么是线程
线程到底是个什么东西,为什么让人如此的着迷?
什么是线程
跟线程相关的概念
说服务端编程还是大量需要并行计算的,而Java也主要占领着服务端市场,那么对Java的并行计算的研究也就显得非常的必要。但首先,我想在这里先介绍几个重要的相关概念。
同步(Synchronous)和异步(Asynchronous)
同步和异步通常用来形容一次方法调用。同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中真实地执行。整个过程不会阻碍调用者的工作。
并发(Concurrency)和并行(Parallelism)
并发和并行都可以表示多个任务一起执行,但是并发是多个任务交替执行,同一时刻只有一个任务在执行,而并行同一时刻可以有多个任务在同时执行。
什么是线程上下文切换
https://zhuanlan.zhihu.com/p/52845869
临界区
临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源就必须等待。
就好比卫生间是临界区,一个人先进去使用并关门上锁。其他的人如果要用此卫生间的话,就得在外等候了,直到用卫生间的人欣然走出😌,释放了卫生间这个资源。
阻塞(Blocking)和非阻塞(Non-Blocking)
阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中进行等待。等待会导致线程挂起,这种情况就是阻塞。此时如果占用资源的线程一直不愿意释放资源,那么其他所有阻塞在这个临界区上的线程都不能工作。
非阻塞的意思与之相反,它强调没有一个线程可以妨碍其他线程执行,所有的线程都会尝试不断前向执行。
线程生命周期
讲线程生命周期之前,我们需要了解线程有哪些状态。其实在Thread中的State枚举
中定义了如下状态:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
NEW
:线程对象被创建,但还没有调用start()方法RUNNABLE
:调用start()
方法后,线程处于runnable状态,但线程调度程序尚未选择它作为正在运行的线程。这个状态其实包含就绪(ready
)和运行中(running
)BLOCKED
:线程在执行中遇到了类似于synchronized同步块,就会进入BLOCKED阻塞状态,这时线程就会暂停执行,直到获得请求的锁WAITING
:进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)TIMED_WAITING
:类似于WAITING,但是它可以在指定的时间过后自行返回TERMINATED
:当run()
方法退出时,该线程已经执行完毕,处于终止或死亡状态。
Java中线程的生命周期由JVM控制,它的状态图如下:
初始(NEW)
线程被创建到start()
被调用这期间,线程的状态是:NEW
。
var thread = new Thread(() -> System.out.println("run"));
System.out.println(thread.getState()); // NEW
可运行(RUNNABLE)
当创建的线程调用了start()
方法,线程的状态从NEW
变成了RUNNABLE
。处于此状态的线程要么正在运行,要么准备运行,但它们正在等待系统的资源分配。
在多线程环境中,线程调度程序(JVM的一部分)为每个线程分配固定的时间片。因此线程会运行一段特定的时间,然后将控制权交给其他RUNNABLE线程
。
var t1 = new Thread(() -> {});
t1.start();
System.out.println(t1.getState());
上述很大可能是打印:RUNNABLE
,原因是第四行的代码是在main
线程中执行,而main
线程和线程t1
可能是并行执行,那么在第四行输出之前,线程t1
没准已经执行完改变了状态。
阻塞(BLOCKED)
当线程当前不符合运行条件时,该线程就处于BLOCKED
状态。当它等待监视器锁并尝试访问被其他线程锁定的代码段时,它会进入此状态。
public class ThreadLifecycle {
public static void main(String[] args) throws InterruptedException {
var t1 = new Thread(new BlockRunnable());
var t2 = new Thread(new BlockRunnable());
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println(t2.getState());
System.exit(0);
}
}
class BlockRunnable implements Runnable {
@Override
public void run() {
synchronized (BlockRunnable.class) {
while (true) {}
}
}
}
上述创建两个线程t1和t2,当t1启动之后首先进入synchronized
代码块中,拿到了BlockRunnable.class
的锁。这里用while模拟t1拥有锁的时间,以便其他线程无法进入此synchronized
方法。而当t2启动时尝试进入synchronized
的代码块,发现锁已经被t1拥有,此时t2线程就处于BLOCKED
的状态。
等待(WAITING)
当线程等待其他线程执行特定操作时,该线程处于WAITING
状态。根据JavaDocs,任何线程都可以通过调用以下三种方法之一进入此状态:
- object.wait()
- thread.join()
- LockSupport.park()
public class WaitingState {
public static void main(String[] args) throws InterruptedException {
var t1 = new Thread(() -> {
synchronized (WaitingState.class) {
try {
System.out.println("before wait.");
WaitingState.class.wait();
System.out.println("after wait.");
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
});
t1.start();
t1.setDaemon(true);
TimeUnit.SECONDS.sleep(1);
System.out.println(t1.getState());
}
}
我们创建线程t1
,并在synchronized
块中调用了wait()
方法。而在main
线程中睡眠了1秒钟,当我们打印线程t1
状态时,保证此刻t1
已经执行完了wait
方法。上述的输出结果为:
before wait.
WAITING
超时等待(TIMED_WAITING)
当线程A等待另一个线程B在规定的时间内执行特定操作时,那么线程A处于TIMED_WAITING
状态。
根据java文档,有以下五种方法可以将线程置于TIMED_WAITING
状态:
- thread.sleep(long millis)
- wait(int timeout) or wait(int timeout, int nanos)
- thread.join(long millis)
- LockSupport.parkNanos(long millis)
- LockSupport.parkUntil(long millis)
public class TimedWaitingState {
public static void main(String[] args) throws InterruptedException {
var t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
});
t1.start();
TimeUnit.SECONDS.sleep(1);
System.out.println(t1.getState());
}
}
我们创建并启动了一个线程t1
,该线程进入睡眠状态,超时时间为5秒,输出将是:TIMED_WAITING
终止(TERMINATED)
当线程完成执行或被异常终止时,它处于TERMINATED
状态。
public class TerminatedState {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
t1.stop();
TimeUnit.SECONDS.sleep(1);
System.out.println(t1.getState());
}
}
我们除了可以使用getState()
得到线程的状态,也可以使用isAlive()
判断线程是否存活。
t1.isAlive();
上述例子返回false
,简而言之:当且仅当线程已启动且尚未死亡时,该线程才处于活动状态,isActive
方法返回true
。
FAQ
单核CPU设定多线程是否有意义?
有意义