线程
一:概念辨析
1:线程与进程
进程:
1:程序由指令和数据组成,指令要执行,数据要读写,就需要将指令加载给cpu,把数据加载到内存,同时程序运行时还会使用磁盘,网络等资源。进程就是负责管理内存,加载指令,管理io的;
2:当一个程序运行时就会将程序的相关代码加载到内存中,这就开启了一个进程;
3:一个程序的实例就是进程,有的程序可以有多个实例进程,有的程序只能有一个实例进程;
线程:
1:一个进程内会有一个或多个线程
2:一个线程就是一个指令流,将指令一条条按照顺序加载给cpu执行;
3:在java中,线程是最小调度单位,进程是最小资源管理单位。在windows中进程是不活动的只是作为线程的容器;
线程和进程对比:
1:进程是相对独立的,线程存在于进程,是进程的一个子集;
2:进程有共享的资源,如共享的内存;
3:进程间的通信,分为同一个计算机和不同计算机:
同一个计算机进行通信较为简单,称为ipc;
不同计算机的进程进行通信需要借助于网络,并且遵守相同的协议如http;
4:线程间通信较为简单,因为他们共享进程的资源;
5:线程更轻量,线程切换上下文比进程切换上下文的成本更低;
2:并发和并行的概念:
并发:在单核cpu下线程实际上是串行执行的,操作系统中有个组件叫任务调度器,它会将cpu的时间片轮流交给不同的线程使用,由于时间片很短,cpu在不同的线程进行切换人类感知不到,所以就会感觉是同时运行的。总结一下就是:微观串行,宏观并行;
像这种,线程轮流使用cpu的情况就叫做并发;
而在多核cpu下,每个核都可以调度线程,这个时候就是并行的;
使用golang的创作者的一句话就是:并发:是同时应对多件事的能力;(deal with)
并行:是同时做多件事的能力;(do)
二:应用
1:异步调用:
同步调用:需要等待结果返回才能执行下面的代码;
异步调用:无需等待结果的返回就能执行下面的代码;
2:提升效率:
注意单核cpu使用多线程与单核cpu使用单线程的性能差距不是很大;
多核cpu和单核cpu执行的性能有很大差别;
三:创建
1:使用thread的内部类
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run() {
log.debug("new thread");
}
};
t.setName("t1");
t.start();
log.debug("main");
}
t.setName(“t1”);来给线程命名。run方法指定要执行的任务,start启动线程;
2:使用runnable配合thread
将线程和任务分开:runnable来定义任务,thread创建线程:
public static void main(String[] args) {
Runnable runnable= new Runnable(){
@Override
public void run() {
log.debug("running");
}
};
Thread thread = new Thread(runnable);
thread.setName("t1");
thread.start();
log.debug("running");
}
java8之后可以使用lamda表达式来简化语句:
public static void main(String[] args) {
/* Runnable runnable= new Runnable(){
@Override
public void run() {
log.debug("running");
}
};*/
Runnable runnable =() ->log.debug("running");
Thread thread = new Thread(runnable);
thread.setName("t1");
thread.start();
log.debug("running");
}
或者:
public static void main(String[] args) {
/* Runnable runnable= new Runnable(){
@Override
public void run() {
log.debug("running");
}
};*/
/* Runnable runnable =() ->log.debug("running");*/
Thread thread = new Thread(()->log.debug("running"),"t1");
/*thread.setName("t1");*/
thread.start();
log.debug("running");
}
3:使用futureTask配合thread
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("running");
return 100;
}
});
Thread thread=new Thread(integerFutureTask,"t1");
thread.start();
log.debug("{}",integerFutureTask.get());
}
futuretask继承了runnable接口,futuretask能够接收callable参数;
调用get方法时会阻塞等待,等待线程执行完毕然后返回结果;
四:查看
1:windows
window查看进程:
- 使用任务管理器查看
- 命令行:tasklist:查看所有进程
- 杀死进程:taskkill
2:linux
linux查看进程:
- ps命令,结合管道运算符。
- top命令动态的查看进程;
杀死进程:
- kill 【进程id】
查看线程:
- top -H -p 【进程id】
-H表示查看线程,-p指定进程id;
3:java
- 查看进程:jps
- 查看线程:jstack 【进程id】
五:原理
上下文切换
-
什么是线程的上下文切换:
当cpu暂停执行当前线程,开始执行下一个线程的时候会产生上下文切换;
-
什么情况下会导致上下文切换
1:cpu的时间片用完
2:垃圾回收
3:优先级更高的线程执行;
4:主动调用一些方法如sleep,yield,join,wait,lock等
当上下文切换时就需要保存当前线程的状态,并且恢复另一个线程的状态。jvm有一个组件叫程序计数器,它的作用是来记录下一条指令的执行地址,程序计数器是线程私有的;
保存线程的状态不止程序计数器,还需要保存虚拟机栈中的栈帧信息;
频繁的上下文切换会影响性能;
六:常见方法
1:start和run方法
start:
public static void main(String[] args) {
new Thread(()->log.debug("start"),"t1").start();
}
结果:
16:20:03.992 [t1] DEBUG org.example.Field.test1 - start
run:
public static void main(String[] args) {
new Thread(()->log.debug("run"),"t1").run();
}
结果:
16:22:07.587 [main] DEBUG org.example.Field.test1 - run
可以看到run没有创建一个新的线程,没法达到异步调用的效果;start可以;
start不能调用多次,不然会报错。
2:sleep
1:状态:
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1");
t1.start();
log.debug("state:{}",t1.getState());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("state:{}",t1.getState());
}
输出:
16:32:08.998 [main] DEBUG org.example.Field.test2 - state:RUNNABLE
16:32:09.512 [main] DEBUG org.example.Field.test2 - state:TIMED_WAITING
可以看到,调用sleep方法之后线程会进入time-waiting状态;
那个线程里调用sleep,那个线程就休眠
2:打断
调用interrupt方法可以打断休眠的线程,但是会抛出异常:
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1");
t1.start();
log.debug("state:{}",t1.getState());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t1.interrupt();
log.debug("state:{}",t1.getState());
}
结果:
16:36:26.912 [main] DEBUG org.example.Field.test2 - state:RUNNABLE
16:36:27.416 [main] DEBUG org.example.Field.test2 - state:TIMED_WAITING
Exception in thread "t1" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
at org.example.Field.test2.lambda$main$0(test2.java:18)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at org.example.Field.test2.lambda$main$0(test2.java:16)
... 1 more
3:yield和sleep的区别
调用yield后线程的状态会从running状态转变为runnable就绪状态状态,然后去调用其他线程;具体的实现依赖于操作系统的任务调度器
和sleep的区别是,调用sleep进入time-wait状态不会被cpu调用,而调用yield进入就绪状态还是有可能被cpu调用的;
3:线程优先级
- 线程的优先级会提示调度器优先执行该线程,但是只是一个提示,调度器可以忽略;
- 当cpu较忙时会给优先级高的线程分配较长的时间片,cpu较闲时,优先级几乎没有作用;
4:join
join:等待线程运行结束;
join(时间):设置最大等待时间,如果超过最大等待时间则不再等待,如果没超过就执行完毕直接返回;
5:interrupt
打断线程
1:打断阻塞:
打断sleep,wait,join的线程;
打断后会抛出异常;
然后打断标记会变成true;
2:打断正常:
打断正常的线程不会使线程停止运行,只会将打断标记变为true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
}
}, "t1");
thread.start();
Thread.sleep(1000);
log.debug("interrupt");
thread.interrupt();
log.debug("is,{}",thread.isInterrupted());
}
18:43:44.479 [main] DEBUG org.example.Field.test3 - interrupt
18:43:44.481 [main] DEBUG org.example.Field.test3 - is,true
但是会一直执行;
可以进行判断然后打断线程:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
Thread thread1 = Thread.currentThread();
if (thread1.isInterrupted()) {
log.debug("被打断了");
break;
}
}
}, "t1");
thread.start();
Thread.sleep(1000);
log.debug("interrupt");
thread.interrupt();
log.debug("is,{}",thread.isInterrupted());
18:48:49.671 [main] DEBUG org.example.Field.test3 - interrupt
18:48:49.672 [t1] DEBUG org.example.Field.test3 - 被打断了
18:48:49.672 [main] DEBUG org.example.Field.test3 - is,true
``