在本系列内容中我们会对JUC做一个系统的学习,本片将会介绍JUC的进程与线程部分
我们会分为以下几部分进行介绍:
在这一小节我们将简单介绍进程与线程
首先我们来简单了解一下程序:
接下来我们才能讲解进程的定义:
我们来简单介绍一下线程:
我们来介绍一下进程与线程之间的区别:
进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
进程拥有共享的资源,如内存空间等,供其内部的线程共享
线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
此外两者的通信方式也不相同:
在这一小节我们将简单介绍并发与并行
首先我们需要了解一下任务调度器:
那么我们的并发实际上就是根据任务调度器来工作的:
通俗来讲:
例如下图:
CPU
时间片 1
时间片 2
时间片 3
时间片 4
core
线程 1
线程 2
线程 3
线程 4
并行的概念就相对而言比较简单了:
通俗来讲:
例如下图:
CPU
时间片 1
时间片 2
时间片 3
时间片 4
core 1
线程 1
线程 1
线程 3
线程 3
core 2
线程 2
线程 4
线程 2
线程 4
在这一小节我们将简单介绍并发与并行
首先我们来简单介绍一下同步与异步:
另外同步操作还有另一个概念:
我们的同步与异步的选择通常会决定程序的运行速度,因而选择同步或异步是非常重要的
我们先来介绍同步与异步的实现方式:
我们再来介绍同步与异步的选择方法:
我们分别给出同步异步的代码展示:
// 同步代码
package cn.itcast.n2;
import cn.itcast.Constants;
import cn.itcast.n2.util.FileReader;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Sync")
public class Sync {
public static void main(String[] args) {
FileReader.read(Constants.MP4_FULL_PATH);
log.debug("do other things ...");
}
}
// 异步代码
package cn.itcast.n2;
import cn.itcast.Constants;
import cn.itcast.n2.util.FileReader;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Async")
public class Async {
public static void main(String[] args) {
new Thread(() -> FileReader.read(Constants.MP4_FULL_PATH)).start();
log.debug("do other things ...");
}
}
我们通常采用异步操作来实现应用的速度提升:
// 例如我们有下面三个操作
计算 1 花费 10 ms
计算 2 花费 11 ms
计算 3 花费 9 ms
汇总需要 1 ms
如果我们采用主线程的同步操作来实现:
// 如果是串行执行,那么总共花费的时间是 10 + 11 + 9 + 1 = 31ms
但是如果我们采用三个CPU的异步操作来实现:
// 但如果是四核 cpu,各个核心分别使用线程 1 执行计算 1,线程 2 执行计算 2,线程 3 执行计算 3
// 那么 3 个线程是并行的,花费时间只取决于最长的那个线程运行的时间,即 11ms 最后加上汇总时间只会花费 12ms
下面我们给出同步异步的实际使用规则:
但是单核多线程也并非是一无是处:
此外多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的 :
最后就是我们的IO操作部分:
这一小节我们将详细介绍线程的具体内容
我们下面将介绍三种创建和运行线程的方法
我们可以直接使用Thread来创建和运行线程:
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
我们再给出一个实际例子:
// 构造方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") {
@Override
// run 方法内实现了要执行的任务
public void run() {
log.debug("hello");
}
};
t1.start();
我们给出实际输出结果:
// 我们会注意到:前面标记了[t1]线程~
19:19:00 [t1] c.ThreadStarter - hello
这里我们将Thread里面的方法采用Runnable类型的方法来代替:
// 创建Runnable类型的方法
Runnable runnable = new Runnable() {
public void run(){
// 要执行的任务
}
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start();
我们给出一个实际例子:
// 创建任务对象
Runnable task2 = new Runnable() {
@Override
public void run() {
log.debug("hello");
}
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
其结果为:
// 结果正常
9:19:00 [t2] c.ThreadStarter - hello
除此之外,我们在JDK8之后,我们可以采用函数式接口Lambda来简化Runnable的书写:
// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
甚至我们都不用定义task,来直接采用Lambda方法书写Thread中的task:
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(() -> log.debug("hello"), "t2");
t2.start();
底层简单解释:
最后我们介绍一下使用Runnable的好处:
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况:
// 创建任务对象(Integer是返回对象)
FutureTask<Integer> task3 = new FutureTask<>(() -> {
log.debug("hello");
return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);
我们给出结果:
19:22:27 [t3] c.ThreadStarter - hello
19:22:27 [main] c.ThreadStarter - 结果是:100
我们给出简单解释:
我们给出单核CPU运行多线程时不断切换进程的状况展示:
// 下述的操作不受我们控制,谁先调用,调用多久都不是我们管控的
@Slf4j(topic = "c.TestMultiThread")
public class TestMultiThread {
public static void main(String[] args) {
new Thread(() -> {
while(true) {
log.debug("running");
}
},"t1").start();
new Thread(() -> {
while(true) {
log.debug("running");
}
},"t2").start();
}
}
我们直接给出结果展示:
23:45:26.254 c.TestMultiThread [t2] - running
23:45:26.254 c.TestMultiThread [t2] - running
23:45:26.254 c.TestMultiThread [t2] - running
23:45:26.254 c.TestMultiThread [t2] - running
23:45:26.254 c.TestMultiThread [t1] - running
23:45:26.254 c.TestMultiThread [t1] - running
23:45:26.254 c.TestMultiThread [t1] - running
23:45:26.254 c.TestMultiThread [t1] - running
23:45:26.254 c.TestMultiThread [t1] - running
23:45:26.254 c.TestMultiThread [t1] - running
由于不同系统的查看方法不同,我们主要介绍三种类型查看方法
我们将会介绍两个与线程底层运行相关的原理
下面我们来介绍一下与进程息息相关的底层原理:
同时栈和栈帧也有一定限制:
我们给出一个简单的代码展示:
// 这里展现的是单线程,目前只有一个栈!
package cn.itcast.n3;
public class TestFrames {
// 首先main方法被调用,所以main方法先进入栈中,在main方法执行结束后被抛出栈
public static void main(String[] args) {
method1(10);
}
// 由于main方法调用了method1,所以栈中在存有main栈帧的同时也将method1栈帧调入,在method1方法执行完毕后抛出
private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
// 由于method1方法调用了method2,所以栈中在存有main,method1栈帧的同时也将method2栈帧调入,method2方法执行完毕后抛出
private static Object method2() {
Object n = new Object();
return n;
}
}
// 这里展现的是多线程,主线程和线程t1独自各占有一个栈,互不影响!
package cn.itcast.n3;
public class TestFrames {
// 这里会产生两个栈,两个栈互不影响,两个栈都会顺序调用main,method1,method2栈帧,顺序不定
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
method1(20);
}
};
t1.setName("t1");
t1.start();
method1(10);
}
private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n;
}
}
我们再来介绍一下上下文切换:
上下文切换措施:
上下文切换时机:
但是我们需要注意:
这一小节我们将介绍线程的各种方法
我们首先给出线程的全部方法一览:
方法
功能
说明
public void start()
启动一个新线程;Java虚拟机调用此线程的run方法
start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException
public void run()
线程启动后调用该方法
如果在构造 Thread 对象时传递了 Runnable 参数,则 线程启动后会调用 Runnable 中的 run 方法,否则默 认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为
public void setName(String name)
给当前线程取名字
public void getName()
获取当前线程的名字。线程存在默认名称:子线程是Thread-索引,主线程是main
public static Thread currentThread()
获取当前线程对象,代码在哪个线程中执行
public static void sleep(long time)
让当前线程休眠多少毫秒再继续执行。Thread.sleep(0) : 让操作系统立刻重新进行一次cpu竞争
public static native void yield()
提示线程调度器让出当前线程对CPU的使用
主要是为了测试和调试
public final int getPriority()
返回此线程的优先级
public final void setPriority(int priority)
更改此线程的优先级,常用1 5 10
java中规定线程优先级是1~10 的整数,较大的优先级 能提高该线程被 CPU 调度的机率
public void interrupt()
中断这个线程,异常处理机制
public static boolean interrupted()
判断当前线程是否被打断,清除打断标记
public boolean isInterrupted()
判断当前线程是否被打断,不清除打断标记
public final void join()
等待这个线程结束
public final void join(long millis)
等待这个线程死亡millis毫秒,0意味着永远等待
public final native boolean isAlive()
线程是否存活(还没有运行完毕)
public final void setDaemon(boolean on)
将此线程标记为守护线程或用户线程
public long getId()
获取线程长整型 的 id
id 唯一
public state getState()
获取线程状态
Java 中线程状态是用 6 个 enum 表示,分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
public boolean isInterrupted()
判断是否被打 断
不会清除 打断标记
我们首先来介绍线程的两个相似的启动方法:
// 首先我们采用start:start方法是启动该线程,线程开始运行
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.start();
log.debug("do other things ...");
}
// 然后我们直接使用run方法:run方法是调用该线程的run方法,实际是main线程在运行t1线程的run方法
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.run();
log.debug("do other things ...");
}
同时我们可以通过查看线程状态来判断start和run的区别:
// 这里我们仅对start判断
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
}
};
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());
}
// 我们可以注意到main状态从就绪状态切换为Runnable
NEW
RUNNABLE
03:45:12.255 c.Test5 [t1] - running...
所以我们给出小结:
直接调用 run 是在主线程中执行了 run,没有启动新的线程
使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
我们首先给出sleep的相关解释:
我们再给出yield的相关解释:
我们采用代码来进行展示:
/*sleep状态转换:我们运行下面代码后可以看到其t1状态从就绪状态到Runnable到Timed Waiting*/
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.debug("wake up...");
e.printStackTrace();
}
}
};
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());
Thread.sleep(2000);
System.out.println(t1.getState());
}
/*sleep打断睡眠线程,抛出异常:注意当睡眠时抛出异常后该程序的打断状态为false*/
public static void main(String[] args) throws InterruptedException {
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();
}
// 输出结果为:
03:47:18.141 c.Test7 [t1] - enter sleep...
03:47:19.132 c.Test7 [main] - interrupt...
03:47:19.132 c.Test7 [t1] - wake up...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.itcast.test.Test7$1.run(Test7.java:14)
/*sleep采用TimeUnit的方法更具有代码可读性*/
@Slf4j(topic = "c.Test8")
public class Test8 {
public static void main(String[] args) throws InterruptedException {
log.debug("enter");
TimeUnit.SECONDS.sleep(1);
log.debug("end");
// Thread.sleep(1000);
}
}
/*yield简单测试:我们需要在Linux虚拟机中使用单核cpu来处理该代码,下面我们同时使用一个cpu处理两个进程*/
@Slf4j(topic = "c.TestYield")
public class TestYield {
public static void main(String[] args) {
Runnable task1 = () -> {
int count = 0;
for (;;) {
System.out.println("---->1 " + count++);
}
};
Runnable task2 = () -> {
int count = 0;
for (;;) {
Thread.yield();
System.out.println("---->2 " + count++);
}
};
Thread t1 = new Thread(task1, "t1");
Thread t2 = new Thread(task2, "t2");
t1.start();
t2.start();
}
}
// 我们会发现task1的count数更多,因为轮到task2时,它会调用yield可能会导致当前线程停止从而去运行另一个线程
---->1 119199
---->2 101074
最后我们讲解一个项目中常用的案例:
// 在没有利用 cpu 来计算时,不要让 while(true) 空转浪费 cpu,这时可以使用 yield 或 sleep 来让出 cpu 的使用权 给其他程序
while(true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// - 可以用 wait 或 条件变量达到类似的效果
// - 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
// - sleep 适用于无需锁同步的场景
// wait实现
synchronized(锁对象) {
while(条件不满足) {
try {
锁对象.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
// do sth...
}
// 条件变量实现
lock.lock();
try {
while(条件不满足) {
try {
条件变量.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// do sth...
} finally {
lock.unlock();
}
下面我们来介绍线程的优先级设置:
我们给出简单代码示例:
// 我们同样需要在单核CPU下进行
@Slf4j(topic = "c.TestYield")
public class TestYield {
public static void main(String[] args) {
Runnable task1 = () -> {
int count = 0;
for (;;) {
System.out.println("---->1 " + count++);
}
};
Runnable task2 = () -> {
int count = 0;
for (;;) {
System.out.println(" ---->2 " + count++);
}
};
Thread t1 = new Thread(task1, "t1");
Thread t2 = new Thread(task2, "t2");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
// 运行结果:我们会发现t2的执行次数明显高于t1
---->1 283500
---->2 374389
首先我们需要了解join的作用:
我们采用一个简单的例子进行解释:
// 下述例子我们希望打印线程t1的r的值,但是我们都知道main和t1线程都是同时运行的,并且t1等待了1s,所以这时的r是没有赋值的,为0
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
});
t1.start();
log.debug("结果为:{}", r);
log.debug("结束");
}
// 但是我们可以选择使用join方法来使主线程等待,知道t1线程结束后再去运行main线程
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
});
t1.start();
log.debug("结果为:{}", r);
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
// 注意我们如果采用sleep或许也可以实现相同的效果,但是很难准确确定其实际线程的结束时刻,所以正常情况下无法使用sleep
此外我们还需要讲解join的其他几个性质:
我们通过几个案例解释上述性质:
// 借助join完成线程之间的同步操作(其实前面第一个例子就是同步操作,我们需要先完成t1线程才能执行main线程的内容)
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
});
t1.start();
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
// 多个join需要等待所有线程完成
// 如下述,一个需要1s,一个需要2s,但由于同时进行,所以我们只需要2s就可以全部执行,然后再执行main
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2);
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
// join是可以设置时效性的,当超过时则不再等待而是直接运行,若不超过就按照线程结束时间计算
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test3();
}
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
// 这里设置时间,若低于1.5s按sleep时间执行;若高于1.5s则在1.5s时执行
sleep(1);
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
// 线程执行结束会导致 join 结束
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
我们首先来简单介绍一下interrupt方法:
我们来进行更详细的介绍:
总而言之我们的打断其实主要分为两种类型,我们采用代码解释:
/* 正常运行情况下打断,并不会影响线程正常运行,但是会将线程的打断标记设置为true */
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
// 这里就是一个正常运行的线程,我们在启动后1s对他进行打断
while(true) {
// 打断不会直接对线程造成影响,但是会将打断状态interrupted变为true(由isInterrupted方法得到)
boolean interrupted = Thread.currentThread().isInterrupted();
// 然后我们可以自行根据打断状态来做对应处理!
if(interrupted) {
log.debug("被打断了, 退出循环");
break;
}
}
}, "t1");
t1.start();
// 1s后实现打断操作
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
}
// 产生结果为:程序不停止,但打断状态为true
20:57:37.964 [t2] c.TestInterrupt - 打断状态: true
/* 不正常状态被打断,例如sleep,yield,join,会使线程进入阻塞状态,抛出异常,会清空打断状态,使其变为false */
private static void test1() throws InterruptedException {
Thread t1 = new Thread(()->{
sleep(1);
}, "t1");
t1.start();
sleep(0.5);
t1.interrupt();
log.debug(" 打断状态: {}", t1.isInterrupted());
}
// 产生结果为:抛出异常,但程序不会停止
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at cn.itcast.n2.util.Sleeper.sleep(Sleeper.java:8)
at cn.itcast.n4.TestInterrupt.lambda$test1$3(TestInterrupt.java:59)
at java.lang.Thread.run(Thread.java:745)
21:18:10.374 [main] c.TestInterrupt - 打断状态: false
最后我们介绍一个用于interrupt的思想模式之两阶段终止:
我们首先给出其逻辑图:
我们通过代码解释:
/*首先利用isinterrupt方法来实现思想*/
// 我们需要书写一个检测类负责不断检测打断状态
class TPTInterrupt {
private Thread thread;
public void start(){
thread = new Thread(() -> {
// 首先不断检测
while(true) {
Thread current = Thread.currentThread();
// 当我们发现被打断时
if(current.isInterrupted()) {
// 自行处理后续内容
log.debug("料理后事");
break;
}
// 当我们发现没有被打断时,我们将程序停止一段时间,并进行简单处理
try {
Thread.sleep(1000);
log.debug("将结果保存");
} catch (InterruptedException e) {
// 这里我们需要注意,如果存在sleep等操作就会导致抛出异常InterruptedException
// 但是抛出异常并不会导致程序结束,也不会导致打断标记为true,
// 所以我们需要手动设置打断标记为true,使其在下一次循环时,中断程序
current.interrupt();
}
// 执行监控操作
}
},"监控线程");
thread.start();
}
public void stop() {
thread.interrupt();
}
}
// 我们在主程序调用:
TPTInterrupt t = new TPTInterrupt();
t.start();
Thread.sleep(3500);
log.debug("stop");
t.stop();
// 我们可以得到结果:
11:49:42.915 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:43.919 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:44.919 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:45.413 c.TestTwoPhaseTermination [main] - stop
11:49:45.413 c.TwoPhaseTermination [监控线程] - 料理后事
/*我们还可以手动设置一个参数来负责线程的关闭*/
// 停止标记用 volatile 是为了保证该变量在多个线程之间的可见性
// 我们的例子中,即主线程把它修改为 true 对 t1 线程可见
class TPTVolatile {
private Thread thread;
private volatile boolean stop = false;
public void start(){
thread = new Thread(() -> {
while(true) {
Thread current = Thread.currentThread();
if(stop) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("将结果保存");
} catch (InterruptedException e) {
}
// 执行监控操作
}
},"监控线程");
thread.start();
}
public void stop() {
stop = true;
thread.interrupt();
}
}
// 我们在主程序中调用:
TPTVolatile t = new TPTVolatile();
t.start();
Thread.sleep(3500);
log.debug("stop");
t.stop();
// 我们得到下属结果:
11:54:52.003 c.TPTVolatile [监控线程] - 将结果保存
11:54:53.006 c.TPTVolatile [监控线程] - 将结果保存
11:54:54.007 c.TPTVolatile [监控线程] - 将结果保存
11:54:54.502 c.TestTwoPhaseTermination [main] - stop
11:54:54.502 c.TPTVolatile [监控线程] - 料理后事
/*我们还可以通过打断 park 线程来实现思想*/
// 首先我们简单介绍一下park:当打断标记为false时,park起到线程暂停作用;当打断标记为true时,继续运行
// 首先我们给出打断标记为false的状态
private static void test3() throws InterruptedException {
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();
}
// 我们可以得到下述结果
21:11:52.795 [t1] c.TestInterrupt - park...
21:11:53.295 [t1] c.TestInterrupt - unpark...
21:11:53.295 [t1] c.TestInterrupt - 打断状态:true
// 如果打断标记已经是 true, 则 park 会失效
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();
}
// 我们可以得到下述结果:
21:13:48.783 [Thread-0] c.TestInterrupt - park...
21:13:49.809 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.812 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.813 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.813 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.813 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
// 我们可以使用 Thread.interrupted() 清除打断状态
// Thread.interrupted()获得当前打断状态,并将打断状态设置为false
// Thread.currentThread().isInterrupted())只能获得当前打断状态,不会影响值
/*
此外还有一些不符合当前状态的打断方式,我们也简单介绍一下:
- 使用线程对象的 stop() 方法停止线程
- stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁, 其它线程将永远无法获取锁
- 使用 System.exit(int) 方法停止线程
- 目的仅是停止一个线程,但这种做法会让整个程序都停止
*/
最后我们给出三个线程的过时方法简单解释:
法名
static
功能说明
stop()
停止线程运行
suspend()
挂起(暂停)线程运行
resume()
恢复线程运行
首先我们简单介绍一下守护线程:
我们给出简单示例:
// 主函数代码:
log.debug("开始运行...");
Thread t1 = new Thread(() -> {
log.debug("开始运行...");
sleep(2);
log.debug("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
sleep(1);
log.debug("运行结束...");
// 运行结果:
08:26:38.123 [main] c.TestDaemon - 开始运行...
08:26:38.213 [daemon] c.TestDaemon - 开始运行...
08:26:39.215 [main] c.TestDaemon - 运行结束...
我们可以简单给出守护线程的一些实例:
这一小节我们将介绍线程的两种状态形式
从操作系统的角度来讲,线程具有五种状态:
我们来简单介绍一下:
从Java虚拟机的角度来看,将其分为六种状态:
我们来简单介绍一下:
我们给出相关代码进行解释:
package cn.itcast.n3;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@Slf4j(topic = "c.TestState")
public class TestState {
public static void main(String[] args) throws IOException {
// 代码完善,但并未运行,属于NEW状态
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
}
};
// 代码完善且一直运行,属于runnable状态
Thread t2 = new Thread("t2") {
@Override
public void run() {
while(true) { // runnable
}
}
};
t2.start();
// 执行一次结束,属于线程执行完毕,TERMINATED 状态
Thread t3 = new Thread("t3") {
@Override
public void run() {
log.debug("running...");
}
};
t3.start();
// 由sleep停止进程,属于timed_waiting状态
Thread t4 = new Thread("t4") {
@Override
public void run() {
synchronized (TestState.class) {
try {
Thread.sleep(1000000); // timed_waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t4.start();
// 由于等待其他进程结束而等待,属于waiting状态
Thread t5 = new Thread("t5") {
@Override
public void run() {
try {
t2.join(); // waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
// 由于等待锁而等待,属于blocked状态
Thread t6 = new Thread("t6") {
@Override
public void run() {
synchronized (TestState.class) { // blocked
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t6.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出其状态
log.debug("t1 state {}", t1.getState());
log.debug("t2 state {}", t2.getState());
log.debug("t3 state {}", t3.getState());
log.debug("t4 state {}", t4.getState());
log.debug("t5 state {}", t5.getState());
log.debug("t6 state {}", t6.getState());
System.in.read();
}
}
我们简单总结一下重点:
到这里我们JUC的进程与线程内容就结束了,希望能为你带来帮助~
该文章属于学习内容,具体参考B站黑马程序员满老师的JUC完整教程
这里附上视频链接:01.001-为什么学习并发_哔哩哔哩_bilibili
手机扫一扫
移动阅读更方便
你可能感兴趣的文章