本次更新修订了旧文,在通俗的基础上增加了专业术语级阐述,更具系统性、专业性 --2020/6/3
后续完整内容陆续更新在本专栏…
文章目录
- 精湛细腻版-Java多线程与并发编程
- 线程与进程
- 并行与并发
- Java线程的使用
- 方式一:直接使用Thread
- 方式二:使用 Runnable 配合 Thread
- Runnable与Thread的关系
- 线程的生命周期(Java1.8)
- Java线程的操作方法
- **Join原理与应用**
- 主线程与守护线程
进程:
线程:
二者区别:
单核 CPU下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是同时运行的 。
总结为一句话就是: 微观串行,宏观并行
一般会将这种 线程轮流使用 CPU 的做法称为并发, concurrent
多个核之间调度线程并执行为并行
创建一个类,声明为 Thread 的子类,重写 Thread 类的 run 方法,该类可以有相关属性及构造器
public class ThreadDemo extends Thread{
private String name; //线程名字
/*构造器*/
public ThreadDemo(String name) {
this.name = name;
}
@Override
public void run() {
//run为线程执行的逻辑体,里面的定义操作就是某个线程具体要执行的东西
}
}
创建线程实体类对象:
在测试类中-new出线程类对象,就可对线程进行相关操作
ThreadDemo td = new ThreadDemo("线程1");
ThreadDemo td1 = new ThreadDemo("线程2");
创建一个类实现 Runnable 接口,实现 run 方法
public class ThreadDemo implements Runnable{
private String name; //线程名字
/*构造器*/
public ThreadDemo(String name) {
this.name = name;
}
@Override
public void run() {
//run为线程执行的逻辑体,里面的定义操作就是某个线程具体要执行的东西
}
}
在测试类中-新建Thread对象,将上述类对象作为参数
Thread t = new Thread(new ThreadDemo("线程1")) ;
Thread t1 = new Thread(new ThreadDemo("线程2")) ;
【了解】Thread 与 Runnable 的关系
方式三:FutureTask配合Thread
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况,这个后面说到
线程运行的原理:
栈与栈帧:属于JVM的内容
线程上下文切换:因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
- CPU给的时间片用完
- Java垃圾回收
- 被更高优先级的线程争抢
- 线程调用了sleep、yield、wait、join、park、synchronized、lock 等方法
频繁上下文切换会影响性能
Thread的run方法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
点进target:
/* What will be run. */
private Runnable target;
可见,如果我们有runable对象的话,Thread实现的是Runnable的run方法
如果没有,直接执行Thread子类的run()
方法
点进Runnable:
@FunctionalInterface //注:这是Jdk8的新特性,注解这个接口为函数式编程接口
public interface Runnable {
/**
* When an object implementing interface Runnable
is used
* to create a thread, starting the thread causes the object's
* run
method to be called in that separately executing
* thread.
*
* The general contract of the method run
is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
最终的run就是一个抽象方法,由开发者去实现
总结:
这是Java线程各个状态之间的切换以及相关API带来的状态切换
补充:操作系统中的线程与Java API中的线程
先来看操作系统:
- 初始状态:仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 可运行状态:(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
- 运行状态:指获取了 CPU 时间片运行中的状态,当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 阻塞状态:排队等待被调度
- 终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
start
此方法将启动线程,将该线程状态变为Java可运行状态
注意:
start() 和run()
为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
join
在多个线程同时运行时,对某个线程调用join方法,t.join()方法会使所有线程都暂停并等待t的执行完毕,该方法可用来实现线程同步
t1.start();
t1.join(); //需放在start后面
Join原理与应用
private static void test1(){ System.out.println("主线程开始..."); //线程1秒后改r=10 Thread t1 = new Thread(()->{ System.out.println("线程一开始..."); //睡眠1s try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } r = 10 ; }) ; t1.start(); //启动线程 System.out.println("r=="+r); }
运行上面代码分析:
主线程开始…
r==0
线程一开始…
- 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
- 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
解决方法:
用 join,加在 t1.start() 之后即可
主线程开始…
线程一开始…
r==10
setPriority
设置线程优先级,在多线程在,对各个线程分配不同的优先级,优先级高的优先执行
Thread类中提供三个级别的常量优先级供快速选择
注意:设置优先级需要在线程启动前
t1.setPriority(Thread.Max_PRIORITY); //最高优先级
ti.start() ;
sleep
线程休眠,为了减少服务器的压力我们需要休眠
只需要在run方法中,需要休眠的地方加入
Thread.sleep(Long xxx)
调用 sleep 会让当前线程从Running进入Timed Waiting状态(属于阻塞态)
其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
睡眠结束后的线程未必会立刻得到执行,还需等待时间片
sleep() 和 wait() 区别
yield
调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
具体的实现依赖于操作系统的任务调度器
Thread.yield();
在run方法中执行
interrupt
如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记(isterrupted()=false);
打断正常运行的线程,打断状态为ture
打断park(暂停)线程,原本属于WAITING,被打断后,打断状态为true
如果打断的是正在运行的线程,则会设置打断标记,给下面方法进行判断
isInterrupted
判断当前线程是否被打断,不会清除打断标记
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束
package c2;
public class TestJoin {
public static void main(String[] args) {
System.out.println("主线程开始...");
//线程1秒后改r=10
Thread t1 = new Thread(()->{
System.out.println("线程一开始...");
//睡眠2s
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("睡眠完毕"); //不会打印,被强制结束
},"daemon_thread") ;
t1.setDaemon(true); //设置t1为守护线程
t1.start(); //启动线程
try {
Thread.sleep(1000); //主线程睡眠1s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("运行结束");
}
}
结果
主线程开始…
线程一开始…
运行结束