详解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线程结束。当前线程才可以继续执行
  }
}


目录
相关文章
|
23天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
17 3
|
23天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
16 2
|
23天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
23天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1
|
23天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
33 1
|
23天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
24 1
|
1月前
|
存储 运维 NoSQL
Redis为什么最开始被设计成单线程而不是多线程
总之,Redis采用单线程设计是基于对系统特性的深刻洞察和权衡的结果。这种设计不仅保持了Redis的高性能,还确保了其代码的简洁性、可维护性以及部署的便捷性,使之成为众多应用场景下的首选数据存储解决方案。
40 1
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
43 1
C++ 多线程之初识多线程
|
2月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
45 6