1.创建线程
(1).直接使用Thread
示例
package com.xiaotian.demo01;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Demo01 {
public static void main(String[] args) {
//创建线程对象
Thread t = new Thread(){
@Override
public void run() {
log.info("t线程");
}
};
//启动线程
t.start();
log.info("main线程");
}
}
执行结果
(2).使用Runnable接口(推荐)
示例
Runnable task = new Runnable() {
public void run() {
log.info("t线程");
}
};
Thread t = new Thread(task, "t1");
t.start();
log.info("main线程");
使用lambda简化
其中@FunctionalInterface表示接口只有一个方法
Runnable task = () -> log.info("t1.线程");
Thread t = new Thread(task, "t1");
t.start();
log.info("main线程");
执行结果
*Thread与Runnable的关系
小结
[1].方法1把线程和任务合并在了一起,方法2是把线程和任务分开了
[2].用Runnable更容易与线程池等高级API配合
[3].用Runnable让任务类脱离了Thread继承体系,更灵活
(3).使用FutureTask类
示例
FutureTask<Integer> task = new FutureTask<>(()->{
log.info("task");
return 100;
});
new Thread( task, "t1").start();
Integer result = task.get();
log.debug("结果是:{}", result);
执行结果
2.查看线程
(1).windows
[1].任务管理器可以查看进程和线程数,也可以用来杀死进程
[2].tasklist查看线程
tasklist | findstr java
[3].taskkill杀死线程
taskkill /f /pid 28060
(2).Linux
[1].ps -fe 查看所有进程
ps -ef|grep java
[2].ps -fT -p 查看某个进程(PID)的所有线程
[3].kill杀死进程
[4].top按大写H切换是否显示线程
[5].top -H -p 查看某个进程的所有线程
(3).Java
[1].jps命令查看所有Java进程
[2].jstack 查看某个Java进程(PID)的所有线程状态
[3].jconsole来查看某个Java进程中线程运行情况
*原理之线程运行
(1).栈与栈帧
Java Virtual Machine Stacks(Java虚拟机栈)
我们知道JVM中由堆,栈,方法区所组成,其中栈内存是是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
[1].每个栈由多个栈帧(Frame)组成,对应着每次方法调用所占用的内存
[2].每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
(2).栈帧图解
package com.xiaotian.demo01;
public class TestFrames {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
method1(10);
}
};
t1.setName("t1");
System.out.println(t1.getState());
t1.start();
t1.start();
System.out.println(t1.getState());
method1(20);
}
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;
}
}
(3).多线程的idea调试
[1].选择调试模式
使用鼠标右击断点,出来如图所示,选择Thread
[2].进行断点调试
可以在如上图所示选择栈帧
(4).线程上下文切换(Thread Context Switch)
因为一下一些原因导致cpu不再执行当前线程,转而执行另一个线程的代码
[1].线程的cpu时间片用完
[2].垃圾回收
[3].有更高优先级的线程需要运行
[4].线程自己调用了sleep, yield, wait, join, park, synchronized, lock等方法
当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器(Program Counter Register), 它的作用是记录下一条jvm指令的执行地址,是线程私有的
[1].状态包括程序计数器,虚拟机栈中每个栈帧的信息,如局部变量,操作数栈,返回地址等。
[2].Context Switch频繁发生影响性能
3.线程API
(1).API概览
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
start() | 启动一个新线程,在新的线程运行run方法中的代码 | start方法只是让线程进入就绪,里面代码不一定立刻运行(CPU的时间片还分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException | |
run() | 新线程启动后调用的方法 | 如果在构造Thread对象时传递了Runnable参数,则线程启动后会调用Runnable中的run方法,否则默认不执行任何操作,但可以创建Thread的子类对象,来覆盖默认方法 | |
join() | 等待线程运行结束 | ||
join(long n) | 等待线程运行结束,最多等待n毫秒 | ||
getId() | 获取线程长整数的,id:唯一id | id唯一 | |
getName() | 获取线程名 | ||
setName(String) | 修改线程名 | ||
getPrority() | 获取线程优先级 | ||
setPrority(int) | 修改线程优先级 | java中规定线程优先级是1~10的整数,较大的优先级提高该线程被CPU调度的几率 | |
getState() | 获取线程状态 | Java中线程状态是用6个enum表示, 分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED | |
isInterrupted() | 判断线程是否被打断 | 不会清楚打断标记 | |
interrupt() | 打断线程 | 如果被打断线程正在sleep, wait, join会导致被打断的线程抛出InterruptedException, 并清除打断标记;如果打断的正在运行的线程,则会设置打断标记,park的线程被打断,也会设置打断标记 | |
interrupted() | static | 判断当前线程是否被打断 | 会清除打断标记 |
currentThread() | static | 获取当前正在执行的线程 | |
sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时让出cpu的时间片给其他线程 | |
yield() | static | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试isAlive()线程是否存活(还没有运行完毕) |
(2).start和run方法
[1].start方法执行前后的线程状态
示例
Thread t1 = new Thread(){
@Override
public void run() {
method1(10);
}
};
t1.setName("t1");
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());
执行结果
[2].多次调用start()方法
示例
Thread t1 = new Thread(){
@Override
public void run() {
method1(10);
}
};
t1.setName("t1");
System.out.println(t1.getState());
t1.start();
t1.start();
System.out.println(t1.getState());
执行结果
(3).sleep与yield
[1].sleep
[a]. 调用sleep会让当前线程从Running进入Timed Waiting状态
示例:
Thread t1 = new Thread("t1"){
@Override
public void run() {
try
{
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
t1.start();
log.debug("t1 state:{}", t1.getState());
Thread.sleep(500);
log.debug("t1 state:{}", t1.getState());
执行结果:
[b].其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出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();
执行结果:
[c].睡眠结束后的线程未必会立刻得到执行
[d].建议使用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
示例
TimeUnit.SECONDS.sleep(1);
[2].yield
[a].调用yield会让当前线程从Running进入Runnable状态,然后调度执行其他同优先级的线程。如果这时没有同优先级的线程,那么不能保证让当前线程暂停的效果
[b].具体的实现依赖于操作系统的任
[3].sleep方法应用
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yied或sleep来让出cpu的使用权给其他程序
while(true){
Thread.sleep(50);
}
[a].可以使用wait或条件达到类似的效果
[b].不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
[c].sleep适用于无需锁同步的场景
(4).线程优先级
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
如果cpu比较忙,那么优先级的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用
示例
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.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
执行结果
(5).join方法
[1].示例
问题
想让r的结果打印为10
static int r = 0;
private static void test1(){
log.debug("start...");
Thread t1 = new Thread(()->{
log.debug("start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end...");
r = 10;
}, "t1");
t1.start();
log.debug("结果为{}", r);
log.debug("end..");
}
public static void main(String[] args) {
test1();
}
但是执行结果都为0
分析
[a].因为主线程和线程t1是并行执行的,t1线程需要1s之后才能算出r=10
[b].而主线程一开始就要打印r的结果,所以只能打印出r=0
解决方法
static int r = 0;
private static void test1() throws InterruptedException {
log.debug("start..");
Thread t1 = new Thread(()->{
log.debug("start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end...");
r = 10;
}, "t1");
t1.start();
t1.join();
log.debug("结果为{}", r);
log.debug("end..");
}
public static void main(String[] args) throws InterruptedException {
test1();
}
[2].同步应用
以调用方角度来讲,如果
[a].需要等待结果返回,才能继续运行就是同步
[b].不需要等待结果返回,就能继续运行就是异步
示例
static int r = 0;
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(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
log.debug("join begin");
t1.join();
log.debug("t1 join end");
t2.join();
log.debug("t2 join end");
long end = System.currentTimeMillis();
log.debug("r1:{}, r2:{}, cost:{}", r1, r2, end-start);
执行结果
分析
[3].限时同步
下面执行得到r1的结果为0
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1:{}, r2:{}, cost:{}", r1, r2, end-start);
(6).interrupt方法
[1].打断阻塞
打断sleep, wait, join的线程
以sleep为例
public static void main(String[] args) {
test1();
}
private static void test1() {
Thread t1 = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
log.debug("打断状态:{}", t1.isInterrupted());
}
执行结果
[2].打断正常
示例
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t2 = new Thread(()->{
while(true){
Thread current = Thread.currentThread();
boolean interrupted = current.isInterrupted();
if(interrupted){
log.debug("打断状态:{}", interrupted);
break;
}
}
}, "t2");
t2.start();
Thread.sleep(500);
t2.interrupt();
}
执行结果:
[3].两阶段终止模式
问题
在一个线程T1中如何"优雅"终止线程T2? 这里的[优雅]指的是给T2一个料理后事的机会
[a].错误思路
[a1].使用线程对象的stop()方法停止线程
stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程永远无法获取锁
[a2].使用System.exit(int)方法停止线程
目的仅是停止一个线程,但这种做法会让整个程序都停止。
[b].分析
[c].实现
package com.xiaotian.demo01;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TwoPhaseTermination {
private Thread monitor;
/**
* 启动监控线程
*/
public void start(){
monitor = new Thread(()->{
while(true){
Thread current = Thread.currentThread();
if(current.isInterrupted()){
log.debug("料理后事");
break;
}
try {
log.debug("执行监控");
Thread.sleep(1000);
} catch (InterruptedException e) {
current.interrupt(); //重新设置标志
e.printStackTrace();
}
}
});
monitor.start();
}
/**
* 停止监控线程
*/
public void stop(){
monitor.interrupt();
}
}
调用:
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
Thread.sleep(3000);
twoPhaseTermination.stop();
执行结果
(7).park方法
[1].示例
Thread t1 = new Thread(()->{
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
[2].执行结果
(8).过时方法(不推荐使用)
方法名 | static | 功能说明 |
---|---|---|
stop() | 停止线程运行 | |
suspend() | 挂起(暂停)线程运行 | |
resume() | 恢复线程运行 |
(9).守护线程
默认情况下,Java进程需要等待所有线程都运行结束,才会借宿。有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
示例
log.debug("开始运行...");
Thread t1 = new Thread(() -> {
log.debug("开始运行..");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("运行结束..");
}, "deamon");
//设置该线程为守护线程
t1.setDaemon(true);
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("运行结束...");
执行结果:
注意:
[1].垃圾回收期线程就是一种守护线程
[2].Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待他们处理完当前请求
4.线程状态
(1).五种状态(从操作系统)
[1].初始状态:仅是在语言层面创建了线程对象,还未与操作系统线程关联
[2].可运行状态:指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行(就绪状态)
[3].运行状态: 指获取了CPU时间片运行中的状态
当CPU时间片用完,会从运行状态切换至可运行状态,会导致线程的上下文切换
[4].阻塞状态:
如果调用了阻塞API,如BIO读写文件,这时线程实际不会用到CPU, 会导致线程上下文切换,进程进入阻塞状态
等BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至可运行状态
与可运行状态的区别是, 对阻塞状态的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
[5].终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转为其他状态
(2).六种状态(Java API层面)
(1).NEW线程刚被创建,但是还没有调用start()方法
(2).RUNNABLE当调用了start()方法之后,注意, Java API层面的RUNNABLE状态涵盖了操作系统层面可运行状态,运行状态和阻塞状态(由于BIO导致的线程阻塞,在Java里无法区分,任然认为是可运行)
(3).BLOCKED, WAITING, TIMED_WAITING都是Java API层面对阻塞状态的细分
(4).TERMINATED当线程代码运行结束
(3).使用java演示六种状态
示例
package com.xiaotian.demo01;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Demo13 {
public static void main(String[] args) {
Thread t1 = new Thread("t1"){
@Override
public void run() {
log.debug("running...");
}
};
Thread t2 = new Thread("t2"){
@Override
public void run() {
while(true){
}
}
};
t2.start();
Thread t3 = new Thread("t3"){
@Override
public void run() {
log.debug("running");
}
};
t3.start();
Thread t4 = new Thread("t4"){
@Override
public void run() {
synchronized (Demo13.class){
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t4.start();
Thread t5 = new Thread("t5"){
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
Thread t6 = new Thread("t6"){
@Override
public void run() {
synchronized (Demo13.class){
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());
}
}
执行结果:
5.线程案例-统筹
阅读华罗庚《统筹方法》,给出烧水泡茶的多线程解决方法,提示
参考下图2,用两个线程(两个人协作)模拟烧水泡茶过程
文中办法乙,丙都相当于任务串行
而图1相当于启动了4个线程,浪费
用sleep(n)模拟洗茶壶,洗水壶等消耗的时间
附: 华罗庚《统筹方法》
统筹方法,是一种安排工作进程的数学方法。它的实用范围极广泛,在企业管理和基本建设中,以及关系复杂的科研项目的组织与管理中,都可以应用。
怎样应用呢?主要是把工序安排好。
比如,想泡壶茶喝。当时的情况是:开水没有;水壶要洗,茶壶、茶杯要洗;火已生了,茶叶也有了。怎么办?
办法甲:洗好水壶,灌上凉水,放在火上;在等待水开的时间里,洗茶壶、洗茶杯、拿茶叶;等水开了,泡茶喝。
办法乙:先做好一些准备工作,洗水壶,洗茶壶茶杯,拿茶叶;一切就绪,灌水烧水;坐待水开了,泡茶喝。
办法丙:洗净水壶,灌上凉水,放在火上,坐待水开;水开了之后,急急忙忙找茶叶,洗茶壶茶杯,泡茶喝。
哪一种办法省时间?我们能一眼看出,第一种办法好,后两种办法都窝了工。
这是小事,但这是引子,可以引出生产管理等方面有用的方法来。
水壶不洗,不能烧开水,因而洗水壶是烧开水的前提。没开水、没茶叶、不洗茶壶茶杯,就不能泡茶,因而这些又是泡茶的前提。它们的相互关系,可以用下边的箭头图来表示:
从这个图上可以一眼看出,办法甲总共要16分钟(而办法乙、丙需要20分钟)。如果要缩短工时、提高工作效率,应当主要抓烧开水这个环节,而不是抓拿茶叶等环节。同时,洗茶壶茶杯、拿茶叶总共不过4分钟,大可利用“等水开”的时间来做。
是的,这好像是废话,卑之无甚高论。有如走路要用两条腿走,吃饭要一口一口吃,这些道理谁都懂得。但稍有变化,临事而迷的情况,常常是存在的。在近代工业的错综复杂的工艺过程中,往往就不是像泡茶喝这么简单了。任务多了,几百几千,甚至有好几万个任务。关系多了,错综复杂,千头万绪,往往出现“万事俱备,只欠东风”的情况。由于一两个零件没完成,耽误了一台复杂机器的出厂时间。或往往因为抓的不是关键,连夜三班,急急忙忙,完成这一环节之后,还得等待旁的环节才能装配。洗茶壶,洗茶杯,拿茶叶,或先或后,关系不大,而且同是一个人的活儿,因而可以合并成为:
看来这是“小题大做”,但在工作环节太多的时候,这样做就非常必要了。
这里讲的主要是时间方面的事,但在具体生产实践中,还有其他方面的许多事。这种方法虽然不一定能直接解决所有问题,但是,我们利用这种方法来考虑问题,也是不无裨益的
实现:
Thread t1 = new Thread(()->{
try {
log.info("洗水壶开始");
Thread.sleep(1000);//洗水壶
log.info("洗水壶结束");
log.info("烧开水开始");
Thread.sleep(15000);//烧开水
log.info("烧开水结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
Thread t2 = new Thread(()->{
try {
log.info("洗茶壶,洗茶杯,拿茶叶开始");
Thread.sleep(4000);
log.info("洗茶壶,洗茶杯,拿茶叶结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.info("喝茶");