线程的创建
- 继承Thread类
public class Test1 {
public static void main(String[] args) {
var t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running");
}
};
t1.start();
log.debug("running");
}
}
- 实现Runnable接口
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Runnable t2 = () -> {
log.debug("running");
};
new Thread(t2, "t2").start();
log.debug("running");
}
}
- 通过FutureTask和Callable接口
@Slf4j(topic = "c.Test3")
public class Test3 {
public static void main(String[] args) {
var task = new FutureTask<>(() -> {
log.debug("running");
return 1;
});
new Thread(task, "t1").start();
try {
log.debug("result:{}", task.get());
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
log.debug("running");
}
}
Thread与Runnable接口的关系
在Thread的成员中有一个名为target的Runnable成员,二者为组合的关系,同时Thread也实现了Runable接口
当我们调用Thread的run()方法时实际上是在调用内部Runnable接口的run()方法,这实际是一种代理模式,实际执行run的都是传入的Runable接口,接着我们查看传入的FutureTask是个什么样子的
FutureTask继承于RunnableFuture,我们继续查看RunnableFuture的结构,
RunnableFuture也是继承了Runnable接口,也就是说三种方法都实现了Runnable任务与Thread体系的解耦
查看正在运行线程的方法
- windows
- tasklist 查看线程
- taskkill 杀死线程
- linux
- ps -fe 查看所有线程
- ps -fT -p
查看某个进程(pid)的所有线程 - kill -9 直接杀死线程
- kill -15 优雅杀死线程
- top 按大写H切换是否显示线程
- top -H -p
查看某个进程(pid)的所有线程
- java
- jpa 查看所有java线程
- jstack
查看某个 Java 进程(PID)的所有线程状态 - jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
run()与start()
直接调用run()方法实际只是调用了这个类的方法,并没有将线程启动,start()方法则是启动新的线程启动执行run()中的方法
@Slf4j(topic = "c.Test4")
public class Test4 {
public static void main(String[] args) {
var t1 = new Thread(() -> {
log.debug("running");
log.debug(Thread.currentThread().getName());
}, "t1");
log.debug("t1 state:{}", t1.getState());
t1.start();
log.debug("t1 state:{}", t1.getState());
log.debug("do something");
var t2 = new Thread(() -> {
log.debug("running");
log.debug(Thread.currentThread().getName());
}, "t2");
log.debug("t2 state:{}", t2.getState());
t2.run();
log.debug("t2 state:{}", t2.getState());
}
}
sleep()与yield()
sleep
- sleep()调用sleep会让当前线程从Runnig进入Timed Waiting状态(阻塞)
- 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议使用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
@Slf4j(topic = "c.Test5")
public class Test5 {
public static void main(String[] args) {
var t1 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
log.debug("t1 state:{}", t1.getState());
t1.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("t1 state:{}", t1.getState());
}
}
这里可以验证第一点
@Slf4j(topic = "c.Test6")
public class Test6 {
public static void main(String[] args) {
var t1 = new Thread(() -> {
log.debug("enter sleeping");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.debug("wake up!");
throw new RuntimeException(e);
}
}, "t1");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("interrupt t1");
t1.interrupt();
}
}
这里可以验证第二点
测试下使用TimeUnit的sleep方法
@Slf4j(topic = "c.Test7")
public class Test7 {
public static void main(String[] args) {
new Thread(() -> {
log.debug("enter");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("end");
}).start();
}
}
yield
- 调用yield会让当前线程从Runnig进入Runnable就绪状态,然后调度执行其他线程
- 具体的实现依赖于操作系统的任务调度器
join方法
这个方法在kolin的协程中也有,和kotlin中的协程其一样的作用,
等待这个线程完成,然后再执行下文的代码
@Slf4j(topic = "c.Test10")
public class Test10 {
private static int r = 0;
public static void main(String[] args) {
log.debug("开始");
var t1 = new Thread(() -> {
log.debug("开始");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("结束");
r = 10;
});
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("结果为:{}", r);
log.debug("结束");
}
}
如果这个join是个有时限的,那么并不会等时限结束,才进行下文代码,而是线程结束后就进行下文代码的执行
@Slf4j(topic = "c.Test12")
public class Test12 {
private static int r1 = 0;
private static int r2 = 0;
public static void main(String[] args) {
var t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
r1 = 10;
});
var start = System.currentTimeMillis();
t1.start();
log.debug("join begin");
try {
t1.join(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
var end = System.currentTimeMillis();
log.debug("结果为:{},{},cost:{}", r1, r2, end - start);
}
}
如果把这里的t1.join()中的参数修改为1,那么将无法得到t1中r1的变化,就进行了下文代码的执行
interrupt()方法
打断sleep,wait,join的线程,这里测试下sleep的方法
@Slf4j(topic = "c.Test13")
public class Test13 {
public static void main(String[] args) {
var t1 = new Thread(() -> {
log.debug("sleep...");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1");
t1.start();
log.debug("interrupt t1");
t1.interrupt();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("打断标记为:{}", t1.isInterrupted());
}
}
这里interrupt标记被重置了
如果我们试图打断一个正常没有sleep等方法的线程则
@Slf4j(topic = "c.Test14")
public class Test14 {
public static void main(String[] args) {
var t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
log.debug("interrupted");
break;
}
}
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t1.interrupt();
log.debug("打断标记为:{}", t1.isInterrupted());
}
}
这里interrupt并没有被重置
还有一个方法的打断表现是很奇怪的
LockSupport的park()方法
@Slf4j(topic = "c.Test15")
public class Test15 {
public static void main(String[] args) {
var t1 = new Thread(() -> {
log.debug("park");
LockSupport.park();
log.debug("unpark");
log.debug("打断标记为:{}", Thread.currentThread().isInterrupted());
//重置打断标记
// Thread.currentThread().interrupt();
LockSupport.park();
log.debug("unpark");
});
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
会执行到打印park后,就出现阻塞的情况
在这里我们调用interrupt()方法后,线程会中断park()方法,且不会出现之前的异常,而且也不会重置interrupt状态,同时如果继续调用park()方法并不会起到中断状态,而是会继续执行,park()方法失效了
如果我们想继续调用下一个park方法的话,需要将线程的interrupt状态自行重置
守护线程与主线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守
护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
@Slf4j(topic = "c.Test16")
public class Test16 {
public static void main(String[] args) {
var t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
log.debug("interrupted");
break;
}
}
log.debug("结束");
}, "t1");
t1.setDaemon(true);
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("结束");
}
}
这里并没有打印守护线程中的结束语句
线程的状态
在常规的操作系统中,线程的状态被分为初始状态,可运行状态,运行状态,阻塞状态
初始状态:仅在语言层面创建了线程对象,还未与操作系统线程关联
可运行状态:(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行
运行状态:指获取了CPU时间片运行中的状态,当CPU时间片用完,会从 运行状态 转换至 可运行状态,会导致线程的上下文切换
阻塞状态:如果调用阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入阻塞状态
等BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至可运行状态
与可运行状态的区别是,对阻塞状态的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
终止状态:表示线程已经执行完毕,声明周期已经结束,不会转换为其他状态
而在java中,线程的状态被分为NEW,RUNNABLE,BLOCKED,WAITING,TIME_WAITING,TERMINATED
只不过Java的线程无法区分操作系统的几种状态,所以可以认为,RUNNABLE包含操作系统中
Q.E.D.