详解Javase 多线程:彻底搞懂线程
Posted 辉常努腻
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解Javase 多线程:彻底搞懂线程相关的知识,希望对你有一定的参考价值。
Javase 详解 多线程:彻底搞懂线程
1.进程、线程(难度:⭐⭐⭐)
1.1什么是进程?什么是线程?
- 进程是一个应用程序(1个进程是一个软件)。
- 线程是一个进程中的执行场景/执行单元。
- 一个进程可以启动多个线程。
1.2(举例)对于Java程序来说,什么是进程?什么是线程?
- 当在DOS命令窗口中输入:Java Helloword 回车之后。
- 首先会先启动JVM,而JVM就是一个进程。
- JVM再启动一个主线程调用main方法。
- 同时再启动一个垃圾回收线程负责看护,回收垃圾。
- 最起码,现在的Java程序中至少有两个线程并发,
- 一个是垃圾回收线程,一个是执行main方法的主线程。
1.2.进程和线程是什么关系?举个例子
- 阿里巴巴:进程
- 马云:阿里巴巴的一个线程
- 保安:阿里巴巴的一个线程
- 京东:进程
- 强东:京东的一个线程
- 妹妹:京东的一个线程
- 进程可以看做是现实生活中的公司
- 线程可以看作是公司中的某个员工
1.2.1注意:
- 进程A和进程B的内存独立不共享(阿里巴巴和京东内存不会共享的!)
- 线程A和线程B呢?
- 在Java语言中:
- 线程A和线程B,堆内存和方法区内存共享。
- 但是栈内存独立,一个线程一个栈。
- 假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行小各自的,这就是多线程并发。
- 在Java语言中:
- 火车站中的每一个售票窗口可以看作是一个线程。
- 我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。
- 所以多线程并发可以提高效率。
- Java中之所以有多线程机制,目的就是为了提高程序的处理效率。
1.3.思考一个问题:
使用了多线程之后,main方法结束,是不是有 不会结束。
main方法结束只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。
1.3.1一个线程一个栈 [ 图 文 ]
1.4.对于单核的CPU来说,真的可以做到真正的多线程并发吗?
- 对于多核的CPU电脑来说,真正的多线程并发是没有问题的。
- 4核CPU表示同一时间点上,可以真正的有4个进程并发执行
- 什么是真正的多线程并发?
- t1线程执行t1的
- t2线程执行t2的
- t1不会影响t2,t2也不会影响t1,这叫做真正的多线程并发。
- 单核的CPU表示只有一个大脑
- 不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
- 对于单核CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人的感觉是:多个事情同时在做!!
1.5.分析程序有几个线程
package com.newXianCheng.XianC01;
/**
* @Description: 分析有几个线程
* @auther:Li Ya Hui
* @Time:2021年5月10日上午10:40:54
*/
public class Test01 {
public static void main(String[] args) {
System.out.println("main begin");
m1();
System.out.println("main over");
}
private static void m1()
{
System.out.println("m1 begin ");
m2();
System.out.println("m1 over ");
}
private static void m2()
{
System.out.println("m2 begin ");
m3();
System.out.println("m2 over ");
}
private static void m3()
{
System.out.println("m3 execute!");
}
}
- 只有一个线程 主栈
- 没有启动分支栈,没有启动分支线程
- 所以这个只有一个主线程
2.实现线程的两种方式(难度:⭐⭐⭐)
(其他后期再补充)
2.1.继承Thread
java支持多线程机制,并且Java已经实现了,我们只需要继承就行了
- 第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法
package com.newXianCheng.ThreadTest02;
/**
* @Description: 实现线程的第一种方式
* 编写一个类,直接继承java.lang.thread 重写run方法
*
* 怎么创建线程对象?
* 怎么启动线程呢?
* @auther:Li Ya Hui
* @Time:2021年5月12日上午8:36:11
*/
public class Test01 {
public static void main(String[] args) {
//这里是main方法,这里的代码属于主线程,在主栈中运行
//新建一个分支线程对象
MyThread myThread = new MyThread();
//启动线程
//myThread.run(); //不会启动线程,不会分配新的分支栈。(这种方式就是单线程)
//start()方法的作用是:启动一个分支线程,在jvm中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
//这段代码的任务只是为了开启一个新的栈空间出来,start方法就结束了,线程就启动成功了。
//启动成功的线程会自动调用run方法,并且run方法在分支线程的栈底部(压栈)
//run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。
myThread.start();
//这里的代码还是运行在主线程中。
for (int i = 0; i < 1000; i++) {
System.out.println("主线程"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//编写程序,这里程序运行在分支线程中(分支栈)。
for (int i = 0; i < 1000; i++) {
System.out.println("分支线程"+i);
}
}
}
2.2.实现Runnable接口实现Run方法
实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
//定义一个可运行的类
class MyRunnable implements Runnable{
public void run() {
}
}
//创建线程对象
Thread t = new Thread(new MyRunnable());
//启动线程
t.start
package com.newXianCheng.RunnableTesto1;
/**
* @Description:实现线程的第二种方式 java.lang.Runnable接口
* @auther:Li Ya Hui
* @Time:2021年5月12日上午10:42:58
*/
public class Test01 {
public static void main(String[] args) {
//创建一个可运行对象
MyRunnable myRunnable = new MyRunnable();
//将可运行的对象封装到一个线程对象
Thread t = new Thread(myRunnable);
//启动线程
t.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
//这并不是一个线程类,是一个可运行的类,他还不是一个线程类。
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。
2.2.1采用匿名内部类可以吗?
package com.newXianCheng.RunnableTesto1;
/**
* @Description: 采用匿名内部类可以吗?
* @auther:Li Ya Hui
* @Time:2021年5月12日上午11:36:02
*/
public class Test02 {
public static void main(String[] args) {
//创建线程对象,采用匿名内部类方式
Thread t = new Thread(new Runnable() {
@Override
public void run() {
}
});
}
}
2.3.run方法和start的区别
- myThread.run(); //不会启动线程,不会分配新的分支栈。(这种方式就是单线程)
- start()方法的作用是:启动一个分支线程,在jvm中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
- 这段代码的任务只是为了开启一个新的栈空间出来,start方法就结束了,线程就启动成功了。
- 启动成功的线程会自动调用run方法,并且run方法在分支线程的栈底部(压栈)
- run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。
run方法运行图
2.4.线程的生命周期
-
新建状态
-
就绪状态
-
运行状态
-
阻塞状态
-
死亡状态
3.线程的一些内置方法(难度:⭐⭐⭐)
3.1如何设置/获取线程的名字
- 获取当前线程对象?
static Thread.currentThread()
class MyThread02 extends Thread{
public void run()
{
for (int i = 0; i < 100; i++) {
//获取当前线程的对象
System.out.println(Thread.currentThread());
}
}
}
- 获取线程对象的名字
String name = 线程对象。getName();
- 修改线程对象名字
线程对象.setName(“线程名字”);
- 当线程没有设置名字的时候,默认的名字有什么规律?(了解一下)
Thread-0
Thread-1
Thread-2
…
package com.newXianCheng.ThreadTest02;
/**
* @Description: 怎么获取当前线程对象
* 怎末获取对象的名字
* 修改线程的名字
* @auther:Li Ya Hui
* @Time:2021年5月12日下午7:52:37
*/
public class Test02 {
public static void main(String[] args) {
//创建线程对象
MyThread02 myThread02 = new MyThread02();
//设置线程的名字
myThread02.setName("tttt");
//获取线程的名字
String name = myThread02.getName();
System.out.println(name);
//启动线程
myThread02.start();
}
}
class MyThread02 extends Thread{
public void run()
{
for (int i = 0; i < 100; i++) {
System.out.println("分支线程-->"+i);
}
}
}
3.2.线程睡眠
- static void sleep(long millis)
- 静态方法 sleep(1000);
- 参数是毫秒
- 作用:让当前的线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用。
- 这行代码出现在A线程,A线程进入睡眠
class MyThread02 extends Thread{
public void run()
{
for (int i = 0; i < 100; i++) {
//获取当前线程的对象
System.out.println(Thread.currentThread().getName());
try {
//让当前线程每次循环运行睡眠1秒
Thread.sleep(1000*1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3.2.2sleep睡眠方法的面试题
为什么分支线程的睡眠方法会让主线程睡眠,因为sleep是静态方法
package com.newXianCheng.ThreadTest02;
/**
* @Description: 关于Thread.slppe的一个面试题
* @auther:Li Ya Hui
* @Time:2021年5月12日下午9:46:25
*/
public class SleepExam {
public static void main(String[] args) throws InterruptedException {
//创建线程对象
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(1);
}
}
});
//尽管是分支线程调用的睡眠,但是因为 sleep是 static
Thread.sleep(1000);
}
}
3.3.终止线程的睡眠
sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程呢?
重点:
- run()方法当中的异常不能throws ,只能try catch
- 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
语法
package com.newXianCheng.ThreadTest02;
/**
* @Description: 唤醒正在睡眠的线程
* @auther:Li Ya Hui
* @Time:2021年5月12日下午9:46:25
*/
public class SleepExam {
public static void main(String[] args) throws InterruptedException {
//创建线程对象
MyThread03 myThread03 = new MyThread03();
Thread thread = new Thread( myThread03);
//尽管是分支线程调用的睡眠,但是因为 sleep是 static
thread.start();
//唤醒线程
thread.interrupt();
}
}
class MyThread03 extends Thread{
public void run()
{
try {
//让当前线程睡眠一年
Thread.sleep(1000*60*60*24*365);
System.out.println("s");
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
}
}
3.4.线程的终止方法
3.4.1.stop方法
- 缺点容易造成数据损坏(不推荐使用)
//终止线程,缺点容易造成数据丢失
thread.stop();
3.4.2.stop方法
- 设置一个布尔标记
- 什么时候想终止,直接改布尔为 false 就可以
package com.newXianCheng.ThreadTest02;
/**
* stop方法不推荐使用
* @Description: 怎末合理的终止一个线程 这种方式是很常用的
* @auther:Li Ya Hui
* @Time:2021年5月13日下午3:29:11
*/
public class Test03 {
public static void main(String[] args) {
//任务
MyRunnable03 myRunnable03 = new MyRunnable03();
//线程类
Thread t = new Thread(myRunnable03);
//线程启动
t.start();
try {
//线程睡眠三秒后
Thread.sleep(3000);
} catch (InterruptedException e) {
}
//终止线程 想要什么时候终止线程t的执行,那么你把标记修改为false,就结束了
myRunnable03.run= false ;
}
}
class MyRunnable03 implements Runnable {
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++)
{
if(run==true)
{
try
{
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" ");
}
catch (InterruptedException e)
{
}
}
}
}
}
3.5.线程调度
3.5.1常见的线程调度模型有哪些?
- 抢占式调度模型
- 哪个线程的优先级比较高,抢到的cpu时间片的概率就高一些/多一些
- java采用的就是抢占式调度模型
- 均分式调度模型
- 平均分配cpu时间片,每个线程占有的cpu时间片时间长度一样。
- 平均分配,一切平等。
- 有一些编程语言,线程调度模型采用的是这种方式
3.5.2.Java中提供了哪些方法是和线程调度有关系的呢?
-
线程优先级
-
线程优先级越高,获得 CPU 时间片的概率就越大,但线程优先级的高低与线程的执行顺序并没有必然联系
-
void setPriority(int newPriority) 设置线程的优先级
-
int getPriority()获取线程优先级
-
最低优先级1
-
默认优先级5
-
最高优先级10
-
优先级比较高的获取cpu时间片可能会多一些(但也不完全是,大概率是多的)
-
语法
-
package com.newXianCheng.ThreadTest02;
/**
* @Description: 线程优先级的使用与讲解 优先级指的是 处于运行状态的时间多一些
* @auther:Li Ya Hui
* @Time:2021年5月13日下午4:46:43
*/
public class Test04 {
public static void main(String[] args) {
//线程静态成员变量
System.out.println("最高优先级"+Thread.MAX_PRIORITY);
System.out.println("最低优先级"+Thread.MIN_PRIORITY);
System.out.println("默认优先级"+Thread.NORM_PRIORITY);
//获取当前线程对象,获取当前线程的优先级
Thread curreThread = Thread.currentThread();
//main线程优先级默认是5
System.out.println(curreThread.getName() + "线程的默认优先级是:"+curreThread.getPriority());
//创建分支线程
Thread t = new Thread(new MyRunnable4());
//调整分支线程优先级
t.setPriority(10);
//调整main线程优先级
Thread.currentThread().setPriority(1);
//启动分支线程
t.start();
//优先级较高的,只是抢到的CPU时间片相对多一些
//大概率方向更偏向于优先级比较高的
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
class MyRunnable4 implements Runnable{
@Override
public void run() {
//获取线程优先级
// System.out.println(Thread.currentThread().getName() + "线程的默认优先级是:"+Thread.currentThread().getPriority());
for (int i = 0; i < 300; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
-
让位方法
-
static void yield()让位方法
-
暂停当前正在执行的线程对象,并执行其他线程
-
yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用。
-
yield ( )方法的执行会让当前从“运行状态”回到就绪状态。
-
注意:再回到就绪之后,有可能还会再次抢到。
-
语法
-
package com.newXianCheng.ThreadTest02;
/**
*以上是关于详解Javase 多线程:彻底搞懂线程的主要内容,如果未能解决你的问题,请参考以下文章