线程基本概念方法
进程与线程
进程
- 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。
- 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
- 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)
线程
- 一个进程之内可以分为一到多个线程。
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行。
- Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器。
二者对比
-
进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
-
进程拥有共享的资源,如内存空间等,供其内部的线程共享
-
进程间通信较为复杂
- 同一台计算机的进程通信称为 IPC(Inter-process communication)
- 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
-
线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
-
线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
并行与并发
单核 cpu 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是 同时运行的 。总结为一句话就是: 微观串行,宏观并行 ,一般会将这种 线程轮流使用 CPU 的做法称为并发
多核 cpu下,每个 核(core) 都可以调度运行线程,这时候线程可以是并行的。
引用 Rob Pike 的一段描述:
- 并发(concurrent)是同一时间应对(dealing with)多件事情的能力
- 并行(parallel)是同一时间动手做(doing)多件事情的能力
创建线程
创建和运行线程方法一,直接使用 Thread
/**
* @author WGR
* @create 2021/1/27 -- 13:24
*/
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
test1();
log.info("main running");
}
public static void test1(){
Thread thread = new Thread("t1") {
@Override
public void run() {
log.info("test1 running");
}
};
thread.start();
}
}
方法二,使用 Runnable 配合 Thread
把【线程】和【任务】(要执行的代码)分开Thread 代表线程,Runnable 可运行的任务(线程要执行的代码)
public static void test2(){
new Thread(() ->{
log.info("runnable running");
}).start();
}
方法三,FutureTask 配合 Thread
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
@SneakyThrows
public static void test3(){
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
log.debug("hello");
return 100;
});
new Thread(futureTask,"t3").start();
System.out.println(futureTask.get());
}
常见方法
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
start() | 启动一个新线 程,在新的线程 运行 run 方法 中的代码 | start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException | |
run() | 新线程启动后会 调用的方法 | 如果在构造 Thread 对象时传递了 Runnable 参数,则 线程启动后会调用 Runnable 中的 run 方法,否则默 认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为 | |
join() | 等待线程运行结 束 | ||
join(long n) | 等待线程运行结 束,最多等待 n 毫秒 | ||
getId() | 获取线程长整型 的 id | id唯一 | |
getName() | 获取线程名 | ||
setName(String) | 修改线程名 | ||
getPriority() | 获取线程优先级 | ||
setPriority(int) | 修改线程优先级 | java中规定线程优先级是1~10 的整数,较大的优先级 能提高该线程被 CPU 调度的机率 | |
getState() | 获取线程状态 | Java 中线程状态是用 6 个 enum 表示,分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED | |
isInterrupted() | 判断是否被打断 | 不会清除 打断标记 | |
isAlive() | 线程是否存活(还没有运行完毕) | ||
interrupt() | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被打断 的线程抛出 InterruptedException,并清除 ;如果打断的正在运行的线程,则会设置 ;park 的线程被打断,也会设置 打断标记 | |
interrupted() | static | 判断当前线程是 否被打断 | 会清除 打断标记,即返回true后,改成false(Thread.interrupted()) |
currentThread() | static | 获取当前正在执 行的线程 | |
sleep(long n) | static | 让当前执行的线 程休眠n毫秒, 休眠时让出 cpu 的时间片给其它 线程 | |
yield() | static | 提示线程调度器 让出当前线程对 CPU的使用 | 主要是为了测试和调试 |
start和run
public static void main(String[] args) {
new Thread(() ->{
log.info("start test....");
},"t1").start();
new Thread(() ->{
log.info("run test....");
},"t2").run();
}
15:53:09.915 c.Test2 [main] - run test....
15:53:09.915 c.Test2 [t1] - start test....
- 直接调用 run 是在主线程中执行了 run,没有启动新的线程
- 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
sleep 与 yield
状态的演示:
@SneakyThrows
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.info("test...");
});
log.info(""+thread.getState());
thread.start();
log.info(""+thread.getState());
}
直接运行,状态是RUNNABLE
@SneakyThrows
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.info("test...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
log.info(""+thread.getState());
thread.start();
Thread.sleep(1000);
log.info(""+thread.getState());
}
当有睡眠的时候,状态变成TIME_WAITING。
@Slf4j(topic = "c.Test3")
public class Test3 {
@SneakyThrows
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("enter sleep...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.debug("wake up...");
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(1000);
log.debug("interrupt...");
t1.interrupt();
}
}
打断正在睡眠的线程会有异常。
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
@Slf4j(topic = "c.Test4")
public class Test4 {
public static void main(String[] args) {
Runnable task1 = () -> {
int count = 0;
for (int x =0;x<10;x++) {
System.out.println("---->1 " + count++);
}
};
Runnable task2 = () -> {
int count = 0;
for (int x =0;x<10;x++) {
// Thread.yield();
System.out.println(" ---->2 " + count++);
}
};
Thread t1 = new Thread(task1, "t1");
Thread t2 = new Thread(task2, "t2");
t1.start();
t2.start();
}
}
假如yield方法后的对比。
join
@SneakyThrows
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("running");
});
log.info("start....");
thread.start();
thread.join();
log.info("end....");
}
可以看出会等t1线程运行结束,主线程才会运行。
@Slf4j(topic = "c.Test6")
public class Test6 {
static int r1 = 0;
static int r2 = 0;
@SneakyThrows
public static void main(String[] args) {
Thread thread = new Thread((() -> {
sleep(1);
r1 = 10;
}));
Thread thread2 = new Thread((() -> {
sleep(2);
r2 = 20;
}));
long start = System.currentTimeMillis();
thread.start();
thread2.start();
thread.join();
thread2.join();
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
}
21:31:45.722 c.Test6 [main] - r1: 10 r2: 20 cost: 2005
等多个线程进行等待的话,会以时间长的位置,和异步编排一样。
interrupt
打断 sleep 的线程, 会清空打断状态,以 sleep 为例
public static void main(String[] args) {
Thread thread = new Thread(() -> {
sleep(1);
});
thread.start();
sleep(0.5);
thread.interrupt();
System.out.println(thread.isInterrupted());
}
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for(;;){
Thread currentThread = Thread.currentThread();
if(currentThread.isInterrupted()){
break;
}
}
});
thread.start();
thread.interrupt();
System.out.println(thread.isInterrupted());
}
打断正常线程会为true。
private static void test3() {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
sleep(0.5);
t1.interrupt();
}
打断 park 线程, 不会清空打断状态
private static void test4() {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
log.debug("park...");
LockSupport.park();
log.debug("打断状态:{}", Thread.interrupted());
}
});
t1.start();
sleep(1);
t1.interrupt();
}
private static void test4() {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
log.debug("park...");
LockSupport.park();
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}
});
t1.start();
sleep(1);
t1.interrupt();
}
二阶段提交
/**
* @author WGR
* @create 2021/1/27 -- 22:26
*/
@Slf4j(topic = "c.Test9")
public class Test9 {
private Thread thread;
private volatile boolean stop = false;
public void start(){
thread = new Thread(() ->{
for(;;){
if(stop){
log.debug("料理后事。。。");
break;
}
try {
Thread.sleep(1000);
log.debug("将结果保存");
} catch (InterruptedException e) {
}
}
}, "t1");
thread.start();
}
public void stop() {
stop = true;
thread.interrupt();
}
public static void main(String[] args) {
Test8 t = new Test8();
t.start();
sleep(2.5);
log.debug("stop");
t.stop();
}
}
/**
* @author WGR
* @create 2021/1/27 -- 21:47
*/
@Slf4j(topic = "c.Test8")
public class Test8 {
private Thread thread;
public void start(){
thread = new Thread(() ->{
for(;;){
Thread t1 = Thread.currentThread();
if(t1.isInterrupted()){
log.debug("料理后事。。。");
break;
}
try {
Thread.sleep(1000);
log.debug("将结果保存");
} catch (InterruptedException e) {
// e.printStackTrace();
t1.interrupt();
}
}
}, "t1");
thread.start();
}
public void stop() {
thread.interrupt();
}
public static void main(String[] args) {
Test8 t = new Test8();
t.start();
sleep(2.5);
log.debug("stop");
t.stop();
}
}
守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守
护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
/**
* @author WGR
* @create 2021/1/27 -- 22:40
*/
public class Test10 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("开始。。。。");
sleep(2);
System.out.println("结束。。。。");
});
thread.setDaemon(true);
thread.start();
sleep(1);
}
}
注意
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求