详解Javase 多线程:彻底搞懂线程

Posted 辉常努腻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解Javase 多线程:彻底搞懂线程相关的知识,希望对你有一定的参考价值。

Javase 详解 多线程:彻底搞懂线程

1.进程、线程(难度:⭐⭐⭐)

1.1什么是进程?什么是线程?

  • 进程是一个应用程序(1个进程是一个软件)。
  • 线程是一个进程中的执行场景/执行单元。
  • 一个进程可以启动多个线程。

1.2(举例)对于Java程序来说,什么是进程?什么是线程?

  1. 当在DOS命令窗口中输入:Java Helloword 回车之后。
  2. 首先会先启动JVM,而JVM就是一个进程。
  3. JVM再启动一个主线程调用main方法。
  4. 同时再启动一个垃圾回收线程负责看护,回收垃圾。
  5. 最起码,现在的Java程序中至少有两个线程并发,
  6. 一个是垃圾回收线程,一个是执行main方法的主线程。

1.2.进程和线程是什么关系?举个例子

  • 阿里巴巴:进程
    • 马云:阿里巴巴的一个线程
    • 保安:阿里巴巴的一个线程
  • 京东:进程
    • 强东:京东的一个线程
    • 妹妹:京东的一个线程
  • 进程可以看做是现实生活中的公司
  • 线程可以看作是公司中的某个员工
1.2.1注意:
  • 进程A和进程B的内存独立不共享(阿里巴巴和京东内存不会共享的!)
  • 线程A和线程B呢?
    • 在Java语言中:
      • 线程A和线程B,堆内存和方法区内存共享。
      • 但是栈内存独立,一个线程一个栈。
    • 假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行小各自的,这就是多线程并发。
  • 火车站中的每一个售票窗口可以看作是一个线程。
  • 我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。
  • 所以多线程并发可以提高效率。
  • Java中之所以有多线程机制,目的就是为了提高程序的处理效率。

1.3.思考一个问题:

使用了多线程之后,main方法结束,是不是有 不会结束。

main方法结束只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。

1.3.1一个线程一个栈 [ 图 文 ]

在这里插入图片描述

1.4.对于单核的CPU来说,真的可以做到真正的多线程并发吗?

  1. 对于多核的CPU电脑来说,真正的多线程并发是没有问题的。
    • 4核CPU表示同一时间点上,可以真正的有4个进程并发执行
  2. 什么是真正的多线程并发?
    • t1线程执行t1的
    • t2线程执行t2的
    • t1不会影响t2,t2也不会影响t1,这叫做真正的多线程并发。
  3. 单核的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!");
	}
}
  1. 只有一个线程 主栈
  2. 没有启动分支栈,没有启动分支线程
  3. 所以这个只有一个主线程

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 多线程:彻底搞懂线程的主要内容,如果未能解决你的问题,请参考以下文章

一文让你彻底搞懂多线程

Java多线程:彻底搞懂线程池

彻底搞懂多线程中各个难点

彻底搞懂多线程中各个难点

彻底搞懂 netty 线程模型

Linux」一篇文章彻底搞懂Linux多线程编程中各个难点