专栏简介: JavaEE从入门到进阶
题目来源: leetcode,牛客,剑指offer.
创作目标: 记录学习JavaEE学习历程
希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长.
学历代表过去,能力代表现在,学习能力代表未来!
认识线程(Thread)
1. 线程是什么?
2. 为什么要有线程?
3. 线程和进程的区别?
4. Java线程和操作系统线程的关系
5. 创建多线程
6. 使用JConsole 查看线程状态
一个线程就是一个执行流 , 每个线程都可以按照自己的顺序来执行代码 , 多个线程同时执行着多份代码.
例如: 一家公司的会计张三去银行办理业务 , 业务范畴很广 , 有财务转账 , 社保缴纳 , 员工福利发放. 如果只有张三一个会计 , 就会耗费很长的时间. 为了节省时间 , 公司张三又叫来了李四和王五 , 三个人分别排队叫号 , 自此就有三个执行流共同完成任务 , 但本质上他们还是办理同一家公司的业务 . 此时我们就把这种情况称为多线程 , 将一个大任务分成多个小任务 , 交给不同执行流分别排队执行.其中李四和王五是张三叫来的 , 那么张三就是主线程.(Main Thread).
首先 , "并发编程" 成为 "刚需".
- 单核CPU的发展遇到了瓶颈 , 想要提高算力 , 需要使用多核CPU. 而并发编程恰好能更充分的利用多核CPU资源.
- 有些任务常见需要等待"IO" , 为了让程序在等待"IO"的同时做一些其他工作 , 也需要用到并发编程.
其次 , 虽然多进程能实现并发编程 , 但线程比进程更轻量.
- 创建线程比进程更快
- 调度线程比进程更快
- 销毁线程比进程更快
Tips:虽然线程比进程更轻量 , 但人们并不满足于此 , 于是有了"线程池"(Thread Pool)和协程(Coroutine)
维度 | 多进程 | 多线程 | 总结 |
数据共享 , 同步 | 数据是分开的 , 共享复杂; 同步简单. |
多线程共享进程数据 , 共享简单; 同步复杂 |
各有优势 |
内存, CPU | 占用内存多 , CPU利用率低 |
占用内存少 , CPU利用率高 |
线程占优 |
创建销毁, 切换 | 创建销毁 , 切换复杂 , 速度慢 | 创建销毁 , 切换简单 , 速度快 | 线程占优 |
编程调试 | 编程简单 , 调试简单 | 编程复杂 , 调试复杂 | 进程占优 |
可靠性 | 进程间不会相互影响 | 一个线程挂掉将导致 整个进程挂掉 |
进程占优 |
分布式 | 适用于多核 , 多机分布; 如果一台机器不够 , 扩展到多台机器比较简单 |
适用于多核分布 | 线程占优 |
- 进程包含线程 , 每个进程中至少有一个线程存在 , 即主线程.
- 进程和进程之间不共享内存空间 , 同一个进程的多个线程之间共用进程的同一份资源.(内存和文件描述符表)
- 进程是系统分配资源的最小单位 , 线程是系统调度的最小单位.
还是之前的例子 , 每个来银行办理业务的客户相当于一个进程 , 他们的票据肯定不同 , 否则银行卡中的钱就被别人取走了 , 而张三李四王五虽然是三个不同的执行流 , 但办理的都是同一家公司的业务 , 所以票据是共享的 , 这就是多线程和多进程最大的区别.
线程是操作系统中的概念 , 操作系统内核实现了线程这样的机制 , 并且提供了一系列的API供用户层使用 , 例如Linux系统的pthread库.
Java标准库中的Thread类可以看做是对操作系统提供的线程API做进一步的抽象和封装.
1) 继承Thread重写run()
t.start 真正创建了一个线程 , 线程是独立的执行流. run() 只是描述了线程要执行的任务是什么 ,
class MyThread extends Thread{
@Override
public void run(){
System.out.println("Hello thread");
}
}
public static void main(String[] args) {
Thread t = new MyThread();
t.start();//start 创建了一个新的线程 , 新的线程负责执行t.run().
System.out.println("Hello main");
}
2) 实现Runnable()
将任务与线程区分开来,让线程和任务之间解耦合.(低耦合) 好处是如果后期要改成多进程 , 或者线程池 , 或者协程.....此时代码改动比较小.
class MyRunnable implements Runnable {
//Runnable 作用 , 描述一个"要执行的任务" , run 方法就是任务执行的细节.
@Override
public void run() {
System.out.println("Hello Thread");
}
}
public static void main(String[] args) {
//描述一个任务
Runnable runnable = new MyRunnable();
//把任务交给线程来执行
Thread t = new Thread(runnable);
t.start();
}
3) 使用匿名内部类
new Thread 创建了 Thread 类的子类 , 因为子类没有名字叫匿名匿名内部类 , 并且让 t 引用指向该实例.
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run(){
System.out.println("Hello thread");
}
};
t.start();
}
4) 使用匿名内部类实现Runnable()
该方法与第二种方法本质相同. 只不过把实现 Runnable 的任务交给匿名内部类.最后将匿名内部类的实例交给 Thread 类的构造方法.
public static void main4(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello thread");
}
});
t.start();
}
5)j简便写法 Lambda表达式
直接把 lambda 传给Thread 构造方法.
public static void main5(String[] args) {
Thread t = new Thread(()->{
System.out.println("Hello world");
});
t.start();
}
JConsole 是 jdk 自带的一种基于JMX的可视化监控 , 管理工具.主要用于查看线程的状态.
打开 jdk 进入bin目录 , 找到 jconsole.
jconsole只能查看正在运行的线程状态 , 我们先在本地 ideal 运行一个多线程的Java程序.这时打开jconsole 找到我们创建的Java程序 , 点击连接.
忽略不安全的连接提示 , 进入线程专栏.
此时我们可以看到多个线程 , 其中main是主线程 , Thread-0是我们创建的另一个线程.其余线程都是JVM自带的.
选择指定的线程后 , 在堆栈跟踪中就可以查看到线程调用栈具体的执行细节. 这对于后期多线程程序的调试工作有很大的用处.