详解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一个线程一个栈 [ 图 文 ]

9.png

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方法运行图

11.png

2.4.线程的生命周期

  • 新建状态
  • 就绪状态
  • 运行状态
  • 阻塞状态
  • 死亡状态

12.png

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;
/**
 * @Description: 让位,当前线程暂停,回到就绪状态,让给其他线程。
 *    静态方法:thrad.yield():
 * @auther:Li Ya Hui
 * @Time:2021年5月13日下午5:18:23
 */
public class Test05 {
  public static void main(String[] args) {
    Thread t = new Thread(new MyRunnable5());
    t.setName("分支线程1");
    t.setPriority(1);
    t.start();
    for (int i = 1; i < 10000; i++) {
      System.out.println(Thread.currentThread().getName() + "-->" + i);
    }
  }
}
class MyRunnable5 implements Runnable{
  @Override
  public void run() {
    for (int i = 1; i < 10000; i++) {
      if (i%100 == 0)
      {       
        // 当前线程暂停一下,让给主线程。  
        //yield方法不是阻塞方法,让当前线程让位,让给其他线程使用。
        //会让当前线程从运行状态回到就绪状态 回到就绪之后,有可能还会再次抢到。
        Thread.yield();
      }
      System.out.println(Thread.currentThread().getName() + "-->" + i);
    }
  }
}

合并线程

  • void join ()合并线程
class MyThread extends Thread{
  public void doSome()
  {
    MyThread2 t = new MyThread2();
    t.join();//当前线程进入阻塞状态,t线程执行,直到t线程结束。当前线程才可以继续执行
  }
}


目录
相关文章
|
3月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
159 0
|
3月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
4月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
296 5
|
8月前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
290 20
|
8月前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
10月前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
174 1
|
11月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
313 0
|
5月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
209 0
|
8月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
141 26
|
8月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
148 17

热门文章

最新文章