目录
前情回顾:操作系统、进程和线程_木子斤欠木同的博客-CSDN博客
1. 认识线程(Thread)
1.1 概念
1.1.1 线程是什么
举个栗子:我们设想如下场景:一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找 来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队, 自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别 排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。
1.1.2 为啥需要线程
- 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源.
- 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.
其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.
- 创建线程比创建进程更快.
- 销毁线程比销毁进程更快.
- 调度线程比调度进程更快.
1.1.3 进程和线程的区别
- 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
- 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
多进程就是例如同时打开QQ音乐和微信,他们连各自为一个进程。多线程就是例如打开微信的聊天功能和朋友圈,他们各自为一个线程。进程是资源分配的基本单位!线程共享同一份资源!
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
1.1.4 Java的线程和操作系统线程的关系
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库)
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装
注:也就是说Java的线程和操作系统的线程是两回事,能思想线程的功能是操作系统内核
1.2 第一个多线程程序
感受多线程程序和普通程序的区别:
- 每个线程都是一个独立的执行流
- 多个线程之间是 "并发" 执行的
class MyThread extends Thread{
@Override
public void run(){
while(true){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
while(true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用jconsole命令观察线程
这是jdk提供的工具,能够让我们查看java进程里面线程的详情!
jconsole只能分析java进程,不能识别非java写的进程~~
除了main和Thread-0两个线程之外,剩下都是JVM自己创建的!
1.3 创建线程的方式(5种)
1.3.1 继承Thread类
class MyThread extends Thread{
@Override
public void run(){
while(true){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
while(true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.3.2 实现Runnable接口
public static void main(String[] args) {
Runnable run = new Thread2();
Thread thread = new Thread(run);
thread.start();
while(true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Thread2 implements Runnable{
@Override
public void run() {
while(true){
System.out.println("hello run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注:以上两种创建线程的区别在于,实现Runnable接口的方法使线程和线程要做的事情分开了,达到了解耦合的作用
1.3.3 继承Thread类,使用匿名内部类
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run(){
System.out.println("hello");
}
};
}
1.3.4 实现Runnable接口,使用匿名内部类
public static void main(String[] args) {
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
}
1.3.5 使用lambda表达式创建线程(推荐)
public static void main(String[] args) {
Thread t3 = new Thread(() ->
System.out.println("hello")
);
}
1.4 多线程的优势—增加运行速度
可以观察多线程在一些场合是可以提高程序整体运行的效率
- 使用 System.currentTimeMillis()可以记录当前系统的 纳秒 级时间戳.
(1)串行
public static void main(String[] args) {
long start = System.currentTimeMillis();
for(int a = 0;a < 1000;a++){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int b = 0;b < 1000;b++){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
(2)并发
public static void main(String[] args) {
Thread t = new Thread(()->{
for(int a = 0;a < 1000;a++){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
long start = System.currentTimeMillis();
t.start();
for(int b = 0;b < 1000;b++){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
2. Thread类及常见方法
Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象,就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
2.1 Thread的常见构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建下线程对象,并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
【了解】Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可 |
2.2 Thread的几个常见属性
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台进程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况,下面我们会进一步说明
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,即简单的理解,为 run 方法是否运行结束了
- 线程的中断问题,下面我们进一步说明
特别说明:isDaemon()方法
true表示是后台线程
false表示是前台进程
(1)后台进程不阻止Java进程结束。哪怕后台线程还没有执行完,Java进程该结束就结束了。
(2)前台线程会阻止Java进程结束,必须得java进程中所有的前台线程都执行完,Java进程才会结束。
(3)创建的线程默认都是前台的,可以通过setDaemon()方法设置为后台。