目录
一、了解和JUC相关的概念
二、Java线程
三、线程共享模型
1.1 什么是JUC?
JUC是java.util.concurrent包的简称,在Java5.0添加,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题!
1.2 什么是进程?
1.3 什么是线程?
进程与线程的对比:
1.4 并发与并行
1.5 同步和异步
从方法调用的角度来讲,如果:
注意:同步在多线程中还有另外一层意思,是让多个线程步调一致
2.1 创建线程的三种方法
方法一:直接使用Thread
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
方法二:使用Runnable配合Thread
Runnable runnable = new Runnable() {
public void run(){
// 要执行的任务
}
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start();
Java 8以后可以使用lambda精简代码:
// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
方法三:FutureTask 配合 Thread
// 创建任务对象
FutureTask task3 = new FutureTask<>(() -> {
log.debug("hello");
return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);
2.2 查看进程线程的方法
windows
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- tasklist 查看进程
- taskkill 杀死进程
linux
- ps -fe 查看所有进程
- ps -fT -p
查看某个进程(PID)的所有线程 - kill 杀死进程
- top 按大写 H 切换是否显示线程
- top -H -p
查看某个进程(PID)的所有线程 Java
- jps 命令查看所有 Java 进程
- jstack
查看某个 Java 进程(PID)的所有线程状态 - jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
2.3 线程有关的常见方法
方法名 | 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
|
|
interrupted()
|
判断是否被打断
|
不会清除 打断标记
|
|
isAlive()
|
线程是否存活(还没有运行完毕)
|
||
interrupt()
|
打断线程
|
如果被打断线程正在 sleep , wait , join 会导致被打断
的线程抛出 InterruptedException ,并清除 打断标
记 ;如果打断的正在运行的线程,则会设置 打断标
记 ; park 的线程被打断,也会设置 打断标记
|
|
interrupted()
|
static
|
判断当前线程是
否被打断
|
会清除 打断标记
|
currentThread()
|
static
|
获取当前正在执行的线程
|
|
sleep(long n)
|
static
|
让当前执行的线
程休眠 n 毫秒,
休眠时让出 cpu
的时间片给其它
线程
|
|
yield()
|
static
|
提示线程调度器
让出当前线程对
CPU 的使用
|
主要是为了测试和调试
|
【start与run方法】
我们通过代码示例可以看出start和run的区别:使用 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 ...");
}
输出:
19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...
19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms
小结:
- 直接调用 run 是在主线程中执行了 run,没有启动新的线程
- 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
【sleep与yield方法】
【jion方法】
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("开始");
Thread.sleep(1000);
log.debug("结束");
r = 10;
});
t1.start();
t1.jion();// 在start后调用join
log.debug("结果为:{}", r);
log.debug("结束");
}
不加 t1.join 输出结果r为 0 ,加了 t1.join() 后输出结果r为 10;原因是加了 t1.join() ,主线程运行到此行后会等待 t1 运行结束后再继续向下运行,即让 main 线程同步等待 t1 线程。
【interrupt方法】
private static void test1() throws InterruptedException {
Thread t1 = new Thread(()->{
log.debug("sleep...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace;
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
log.debug(" 打断标记: {}", t1.isInterrupted());
}
2、打断正常运行的线程时:不会清空打断状态,即打断标志置为true;同时,interrupt打断正常运行的线程时,不会让线程停下来,线程会继续执行。若想让线程停下来,需要根据对打断标志为 true 的判断从而手动让线程停下来。
private static void test2() throws InterruptedException {
Thread t1 = new Thread(()->{
while(true) {
Thread current = Thread.currentThread();
if(current.isInterrupted()) {
log.debug("被打断了,退出循环");
break;
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
下面来一道关于interrupt的常见面试题:在一个线程T1中如何优雅地终止线程T2?这里的优雅是指给T2一个料理后事的机会。
1. 如果使用线程的 stop() 方法停止线程:stop() 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当他被杀死后就再也没有机会释放锁,其他线程将永远无法获取该锁。(容易破坏代码块,造成死锁的方法还有suspend():挂起/暂停线程运行、resume():恢复线程运行,这些方法已经过时)
2. 若使用 System.exit(int) 方法停止线程:该方法是让整个进程都停止,而我们只想要一个线程通知,这种做法明显不划算。
此时,使用 interrupt() 方法的两阶段终止模式为最优解:
代码实现如下:
class TwoPhaseTermination{
private Thread monitor;
// 启动监控线程
public void start() {
monitor = new Thread(() -> {
while(true) {
Thread current = Thread.currentThread();
if(current.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);// 情况1:中断发生在线程睡眠时,会在catch中抛出异常,中断标志为false
log.debug("执行监控记录");// 情况2:中断发生在正常运行线程时,中断标志置为true
} catch (InterruptedException e) {
e.printStackTrace();
// 重新设置打断标志
current.interrupt();
}
}
});
monitor.start();// 启动线程
}
// 停止监控线程
public void stop() {
monitor.interrupt();
}
}
【守护线程】
线程对象名.setDaemon(true);// 设置守护线程
守护线程示例:
2.4 线程的五种状态
2.5 线程的六种状态
线程共享模型详解