提示:本文带大家入门JavaSE基础的多线程部分,更深层次建议看书籍《Java并发编程》
全文主要内容为:
1.多线程的生命周期原理
2.多线程的常用API:面试常问
3.并发和并行之间的联系
本人能力有限,如有遗漏或错误,敬请指正,谢谢
文章目录
其他文章
1.Java多线程基本概念和常用API(面试高频)
2.线程安全问题(synchronized解决,各种类型全)
3.Java并发线程池使用和原理(通俗易懂版)
4.基于SpringBoot+Async注解整合多线程
前言
学习一门技术最好使用wwh方法
what:这门技术是什么
why:为什么用这个技术,使用会有什么优化
how:怎么使用
提示:以下是本篇文章正文内容,下面有代码案例可供参考
一、进程和线程关系
1.1 基本概念
不打算按照网上其他文章以概念的形式解释这个关系。
第一个例子:电脑上打开一个微信,就启动了一个进程(可以在任务管理器查看),所以进程就是一个运行的程序,那么在微信上你可以打开小程序或者朋友圈,那么你打开这两个是不冲突的,这两个也就是两个线程。
第二个例子:进程就相当于一个公司,线程相当于公司的员工,一个公司里面有很多个员工,也就是说一个进程可以启动多个线程
1.2 二者内存关系
一个进程中线程A和线程B独立开辟一个栈内存,但是堆内存和方法区共享。一个线程一个栈
1.3并行和并发
引用其他博主的,这篇写的通俗易懂,大家可以移步学习后再回来
https://www.cnblogs.com/Hei-Tao-K/p/10142561.html
二、常用API:可以复制代码自己尝试结果
2.1 启动线程用start()方法
start()方法作用是:启动一个分支线程,在JVM中开辟新的栈空间,只要新的栈空间开辟出来后,start()方法就结束了。启动成功后会自动调用run()方法
run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码;程序中只有主线程这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
2.2 sleep()方法
作用:让当前线程进入休眠期
如果没有写在分线程run()方法中,而是主线程main中,那么是让主线程休眠,不是分线程
写在哪个线程里就让哪个线程睡眠
public class Test{
public static void main(String[] args){
MyThread my1=new MyThread();
//分线程my1开启
my1.start();
try{
//这里的sleep()方法没有放在run()中,所以作用在主线程中:睡眠10s
Thread.sleep(10000);
}catch(InterruptedException e){
e.printStackTrace();
}
//主线程main:由于上面主线程睡眠10s,所以分线程先会执行,10s后主线程加入进来
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}
//静态内部类
public static class MyThread extends Thread{
public void run(){
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}
2.3 合理终止一个线程执行
原理:加一个flag标志位
public class Test{
public static void main(String[] args){
MyThread my1=new MyThread();//创建MyThread对象
my1.start();//启动分线程
try {
Thread.sleep(5000);//主线程睡眠5秒
} catch (InterruptedException e) {
e.getMessage();
}
my1.flag=false;//线程睡眠5秒后将标志位变成false,退出循环,即线程中断
}
}
public static class MyThread extends Thread{
boolean flag=true;//标志位
public void run(){
for(int i=0;i<10;i++){
//相当于让flag当作一个控制器,flag为true就是开启,false就是关闭
if(flag){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
return;
}
}
}
}
2.4获取当前线程信息
//获取当前线程
Thread t1=Thread.currentThread();
//获取当前线程的名字
String name=t1.getName();
//设置当前线程的名字
t1.setName("设置线程名字");
2.5线程调度相关方法
该知识点关键:优先级比较高的只是获取时间片的概率更大一点,并不是优先级高的一定获取时间片
常见的线程调度模型:
- 抢占式调度模型:
哪个线程优先级高,抢到的CPU时间片的概率就会高一些,JAVA采用的就是抢占式模型。
- 均分式调度模型:
平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样,平均分配,一切平等,有一些编程语言采用这种方式
最低的优先级为1 最高的优先级为10 默认的优先级为5
void setPriority(int new Priority) 设置线程的优先级
int getPriority()获取线程的优先级
2.6 线程让步
(谁调用这个方法就是谁让步)
Thread.yield();
先来说说多个线程启动时,是谁先执行呢?
在Java中采取的是抢占式调度模型,举个例子,有一台电脑,三个人想玩,但这台电脑每次只能由一个人玩3分钟,所以这三个人就要抢到这个电脑位置,等三分钟后,三个人再重新抢位置。
这三个人也就是三个线程,电脑位置也就是CPU时间片,当某个线程抢夺到时间片,就能执行run()方法。
线程让步:以上面的例子来说,抢到位置的人突然不想玩了(ps:他只是想体验一下抢位置的感觉),所以这时候三个人又重新抢位置
static void yield() 暂停当前正在执行的线程
yield()方法不是阻塞方法,让当前线程让位, 给其他线程使用,yeld()方法的执行使得线程从“运行状态”变回“就绪状态”。
注意:yield()方法让执行该方法的线程放弃当前对CPU的执行权,并让所有线程开始抢夺CPU的执行权。此时每一条优先级最高的线程都有可能抢到CPU的执行权,包括刚刚放弃了执行权的线程!
2.7线程合并
void join() :中途加入其他线程,其他线程执行完了才轮到本线程,继续抢夺cpu
注意:线程必须要先启动了,才能中途加入,只有启动了,才能对线程进行操作。
举例子:小明正在看电视剧,看到一半,中途突然加入了广告,但是不能点掉,只能等广告结束后,再继续回到电视剧。可以理解为插队
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("线程插队"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 200; i++) {
//当主线程执行到100的时候,让线程插队执行完之后,再执行
if (i == 100) {
thread.join();
}
System.out.println("主线程"+i);
}
}
}
三、多线程对象的生命周期
1.新建状态: Thread t=new Thread(runnable) 刚new对象出来就是新建状态,此时因为没有调用start()方法,所以还没有开辟新的线程
2.就绪状态: t.start() 当Thread对象调用start()方法后,就会在内存开辟一块栈,开辟完后就进入就绪状态,此时具有抢占cpu时间片的权利
3.运行状态: 线程抢到cpu时间片后,就会执行run()方法,此时进入运行状态
4.阻塞状态: 如果在运行的过程中,碰到了sleep()或者wait()或者锁,就会中途放弃时间片,此时run()方法暂停执行,等阻塞完后,又回到就绪状态,继续抢占时间片
5.死亡状态: 当执行完run()方法了或者出异常了线程就会进入死亡状态