发布于2021-03-13 14:24 阅读(1200) 评论(0) 点赞(5) 收藏(4)
- 进程基本相互独立,而线程存在进程内,是进程的子集。
- 进程有共享的资源,如内存空间,内部线程可以共享。
- 不同计算机的进程通信需要通过网络,共同遵守网络协议如HTTP。
- 线程通信相对简单,共享进程的内存,多个线程可以访问同一个共享变量。
- 线程更加轻量,其上下文切换成本低于进程。
单核CPU下,线程实际还是串行执行
的,操作系统中有一个组件名为任务调度器,将CPU的时间片(Windows下时间片最小约为15毫秒)分给不同的线程使用,只是由于CPU在线程间(时间片很短)的切换非常快,人类感觉就是同时运行的,即微观串行,宏观并行
线程轮流使用CPU
的做法被成为并发(Concurrent)
CPU | 时间片1 | 时间片2 | 时间片3 | 时间片4 |
---|---|---|---|---|
core | 线程1 | 线程2 | 线程3 | 线程4 |
多核CPU下,每个核(Core)
都可以调度运行线程,称之为并行(parallel)
。
下述两个核心并行,每个核心并发执行线程
CPU | 时间片1 | 时间片2 | 时间片3 | 时间片4 |
---|---|---|---|---|
core1 | 线程1 | 线程2 | 线程3 | 线程4 |
core2 | 线程2 | 线程4 | 线程1 | 线程3 |
同步
异步
@Slf4j
public class ThreadExecrise {
public static void main(String[] args) {
//创建线程1
Thread thread1 = new Thread(){
public void run(){
//要执行的任务
log.debug("我是新线程");
}
};
//修改新线程名字
thread1.setName("线程1");
// 启动线程1
thread1.start();
log.debug("我是main线程");
}
}
优点:把线程和任务(执行的代码)分开
Runnable runnable = new Runnable() {
public void run() {
log.debug("我是新线程");
}
};
Thread thread1 = new Thread(runnable,"线程1"); //创建线程
thread1.start();
lambda简化
因为Runnable是函数式接口,即接口中只含有一个抽像方法,因此可以简化。
//lambda简化
Runnable runnable = () -> log.debug("我是新线程");
Thread thread1 = new Thread(runnable,"线程1"); //创建线程
thread1.start();
接收Callable类型的参数,用来处理有返回值的情况
FutureTask<Integer> task = new FutureTask<Integer>(() -> {
log.debug("我是新线程!");
Thread.sleep(2000);
return 100;
});
new Thread(task,"线程1").start();
//main线程获取返回值,main线程阻塞,同步等待task执行完毕
Integer res = task.get();
log.debug("the result is {}",res); //100
@Slf4j
public class runAndStart {
public static void main(String[] args) {
Thread t1 = new Thread(() -> log.debug("running。。。"));
// t1.run();//并没有开启新线程
t1.start(); //开启了新线程
// t1.start(); //异常,不能多次开启一个线程
}
}
小结
直接调用 run()
是在主线程中执行了 run()
,没有启动新的线程
使用 start()
是启动新的线程,通过新的线程间接执行 run()
方法中的代码
sleep
InterruptedException
异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】sleep()
代替 Thread 的 sleep()
来获得更好的可读性yield
小结
yield使cpu调用其它线程,但是cpu可能会再分配时间片给该线程;而sleep需要等过了休眠时间之后才有可能被分配cpu时间片
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
cpu 比较忙,那么优先级高的线程会获得更多的时间片,
cpu 闲时,优先级几乎没作用
join再当前线程方法中由其他线程调用,会等待调用方法的线程结束任务后执行
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
t1.join(); // 不加join的情况下,多核CPU并行执行,r=0,在主线程main方法中加入t1线程调用join方法后,主线程需等待t1线程任务结束执行
log.debug("结果为:{}", r);// r=10
log.debug("结束");
}
打断 sleep,wait,join和正常运行的线程
sleep,wait,join 的线程,这几个方法都会让线程进入阻塞状态, interrupt 方法打断这些线程的阻塞,抛出异常并且清空打断状态,即调用
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("t1线程任务开始sleep...");
try {
Thread.sleep(1000); // wait, join 方法都如此
} catch (InterruptedException e) {
//e.printStackTrace();
log.debug("t1线程被打断了"); //被打断后,返回异常处理结果
}
}, "t1");
t1.start();
Thread.sleep(500);
log.debug("主线程打断t1线程");
t1.interrupt(); //打断t1线程的睡眠,并清除打断状态
Thread.sleep(10); //阻塞的线程打断之后需要等会会被清除打断状态
log.debug("线程t1的状态为{}",t1.isInterrupted()); //false
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("t1线程任务开始循环...");
while (true){
boolean flag = Thread.currentThread().isInterrupted();
//如果打断状态为真,就停止程序
if(flag)
break;
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("主线程开始打断");
t1.interrupt(); //打断t1线程的睡眠,并清除打断状态
Thread.sleep(10); //正常运行的线程打断之后需要等会并不会会清除打断状态
log.debug("线程t1的状态为{}",t1.isInterrupted()); //true
}
sleep,yiled,wait,join 对比
关于join的原理和这几个方法的对比:看这里
补充:
- sleep,join,yield,interrupted是Thread类中的方法
- wait/notify是object中的方法
sleep 不释放锁、释放cpu
join 释放锁、抢占cpu
yiled 不释放锁、释放cpu
wait 释放锁、释放cpu
Two Phase Termination,就是考虑在一个线程T1中如何优雅地终止另一个线程T2?这里的优雅指的是给T2一个料理后事的机会(如释放锁)。
如下所示:那么线程的isInterrupted()
方法可以取得线程的打断标记,
如果线程在睡眠sleep
期间被打断,打断标记是不会变的,为false,但是sleep
期间被打断会抛出异常,我们据此
手动设置打断标记为true
;
如果是在程序正常运行期间被打断的,那么打断标记就被自动设置为true
。
处理好这两种情况那我们就可以放心地来料理后事啦!
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
TwoParseTermination twoParseTermination = new TwoParseTermination();
twoParseTermination.start();
Thread.sleep(3000); // 让监控线程执行一会儿
twoParseTermination.stop(); // 停止监控线程
}
}
@Slf4j
class TwoParseTermination {
private Thread thread;
public void start() {
thread = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
log.debug("线程结束。。正在料理后事中");
break;
}
try {
Thread.sleep(500);
log.debug("正在执行监控的功能");
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); //避免监控线程睡眠时被监控线程被打断
}
}
},"monitorThread");
thread.start();
}
public void stop() {
thread.interrupt();
}
}
@Slf4j
public class Test02 {
public static void main(String[] args) throws InterruptedException {
test3();
}
private static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park停止该线程...");
LockSupport.park();//停止线程后,该线程一直处于park状态,直到被打断
log.debug("unPark");
log.debug("打断状态{}", Thread.currentThread().isInterrupted());
LockSupport.park(); //此时该线程被打断,但因其正常运行不会标记为false,因此失效(标记为true即失效)
log.debug("unPark1");
log.debug("打断状态{}", Thread.interrupted()); //返回为假,park方法会再次生效,类似i++,此行为true,但是下行已改false
LockSupport.park();
log.debug("unPark2");
},"t1");
t1.start();
Thread.sleep(1000);
t1.interrupt(); //被打断后t1线程会恢复继续运行
}
默认情况下,java进程需要等待所有的线程结束后才会停止。
但是有一种特殊的线程,叫做
守护线程
,在其他线程全部结束的时候即使守护线程还未结束代码未执行完java进程也会停止。
普通线程t1可以调用t1.setDeamon(true);
方法变成守护线程
垃圾回收器线程就是一种守护线程
Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等
待它们处理完当前请求
其划分主要是从操作系统的层面进行划分的
Thead thread = new Thead();
,还未与操作系统线程关联这是从 Java API 层面来描述的,我们主要研究的就是这种。状态转换详情图:地址
根据 Thread.State 枚举,分为六种状态
NEW 跟五种状态里的初始状态是一个意思
RUNNABLE 是当调用了 start()
方法之后的状态,注意,Java API 层面的 RUNNABLE
状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【IO阻塞状态】(由于 BIO 导致的线程阻塞(如读取文件),在 Java 里无法区分,仍然认为是可运行)
BLOCKED
, WAITING
, TIMED_WAITING
都是 Java API 层面对【阻塞状态】的细分,
BLOCKED
WAITING
TIMED_WAITING
等待一段时间之后,会唤醒线程去重新获取锁,区别WAITING永久等待
Thread.sleep(long)
Object.wait(long)
Thread. Join(long)
LockSupport.parkNanos()
LockSupport.parkUntil()
任务管理器可以查看进程和线程数,也可以杀死进程
tasklist查看进程
taskkill杀死进程
常用参数
/F 指定强制终止进程。
/PID 指定要终止的进程的 PID。(使用 TaskList 取得 PID)
ps -fe 查看运行的进程
ps -fe | grep ef 筛选名字含有ef的进程
top -H -p 【进程的PID】 查看当前进程的所有线程
jps命令查看所有java进程
jstack 【PID】查看某个Java进程的所有线程状态
jconsole 来查看某个Java进程中线程的运行情况(图形界面)
Java Virtual Machine Stacks(Java 虚拟机栈)
JVM中由堆,栈,方法区组成,其中栈内存被线程所使用,每个线程启动后,虚拟机就会为其分配一块栈内存。
因为以下一些原因导致CPU不再执行当前的线程,转而执行另一个线程的代码
当Context Switch发送时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的就是程序计数器(Program Counter Register),其作用是记住下一条jvm指令的执行地址,是线程私有的。
原文链接:https://blog.csdn.net/m0_46975599/article/details/114702798
作者:天天在家
链接:http://www.javaheidong.com/blog/article/114270/7f21c0afad412550b607/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!